diff --git a/Moose Development/Documentation/DCS_ControlAPI.txt b/Moose Development/Documentation/DCS_ControlAPI.txt new file mode 100644 index 000000000..a0650d801 --- /dev/null +++ b/Moose Development/Documentation/DCS_ControlAPI.txt @@ -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 + + 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 diff --git a/Moose Development/Documentation/Scoring/Scoring Sample.csv b/Moose Development/Documentation/Scoring/Scoring Sample.csv new file mode 100644 index 000000000..49c85640d --- /dev/null +++ b/Moose Development/Documentation/Scoring/Scoring Sample.csv @@ -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 diff --git a/Moose Development/Documentation/Scoring/Scoring Sample.xlsx b/Moose Development/Documentation/Scoring/Scoring Sample.xlsx new file mode 100644 index 000000000..4aca4870c Binary files /dev/null and b/Moose Development/Documentation/Scoring/Scoring Sample.xlsx differ diff --git a/Moose Development/LDT External Tools/Moose LOADER Generate Dynamic Moose.lua.launch b/Moose Development/LDT External Tools/Moose LOADER Generate Dynamic Moose.lua.launch index ffc89c77d..b638009bb 100644 --- a/Moose Development/LDT External Tools/Moose LOADER Generate Dynamic Moose.lua.launch +++ b/Moose Development/LDT External Tools/Moose LOADER Generate Dynamic Moose.lua.launch @@ -3,7 +3,7 @@ - + diff --git a/Moose Development/LDT External Tools/Moose LOADER Generate Static Moose.lua.launch b/Moose Development/LDT External Tools/Moose LOADER Generate Static Moose.lua.launch index b8a402dc5..4da52623c 100644 --- a/Moose Development/LDT External Tools/Moose LOADER Generate Static Moose.lua.launch +++ b/Moose Development/LDT External Tools/Moose LOADER Generate Static Moose.lua.launch @@ -3,7 +3,7 @@ - + diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua index 098c3964e..aee68be5c 100644 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ b/Moose Development/Moose/AI/AI_A2A.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 64846ae01..a38ac5b58 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 316a7ab12..dab1e5522 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3,17 +3,159 @@ -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia1.JPG) -- -- ==== +-- +-- # QUICK START GUIDE +-- +-- There are basically two classes available to model an A2A defense system. +-- +-- AI\_A2A\_DISPATCHER is the main A2A defense class that models the A2A defense system. +-- AI\_A2A\_GCICAP derives or inherits from AI\_A2A\_DISPATCHER and is a more **noob** user friendly class, but is less flexible. +-- +-- Before you start using the AI\_A2A\_DISPATCHER or AI\_A2A\_GCICAP ask youself the following questions. +-- +-- ## 0. Do I need AI\_A2A\_DISPATCHER or do I need AI\_A2A\_GCICAP? +-- +-- AI\_A2A\_GCICAP, automates a lot of the below questions using the mission editor and requires minimal lua scripting. +-- But the AI\_A2A\_GCICAP provides less flexibility and a lot of options are defaulted. +-- With AI\_A2A\_DISPATCHER you can setup a much more **fine grained** A2A defense mechanism, but some more (easy) lua scripting is required. +-- +-- ## 1. Which Coalition am I modeling an A2A defense system for? blue or red? +-- +-- One AI\_A2A\_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. +-- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI\_A2A\_DISPATCHER **objects**, +-- each governing their defense system. +-- +-- +-- ## 2. Which type of EWR will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). +-- +-- The MOOSE framework leverages the @{Detection} classes to perform the EWR detection. +-- Several types of @{Detection} classes exist, and the most common characteristics of these classes is that they: +-- +-- * Perform detections from multiple FACs as one co-operating entity. +-- * Communicate with a Head Quarters, which consolidates each detection. +-- * Groups detections based on a method (per area, per type or per unit). +-- * Communicates detections. +-- +-- ## 3. Which EWR units will be used as part of the detection system? Only Ground or also Airborne? +-- +-- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. +-- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). +-- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. +-- The position of these units is very important as they need to provide enough coverage +-- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. +-- +-- ## 4. Is a border required? +-- +-- Is this a cold car or a hot war situation? In case of a cold war situation, a border can be set that will only trigger defenses +-- if the border is crossed by enemy units. +-- +-- ## 5. What maximum range needs to be checked to allow defenses to engage any attacker? +-- +-- A good functioning defense will have a "maximum range" evaluated to the enemy when CAP will be engaged or GCI will be spawned. +-- +-- ## 6. Which Airbases, Carrier Ships, Farps will take part in the defense system for the Coalition? +-- +-- Carefully plan which airbases will take part in the coalition. Color each airbase in the color of the coalition. +-- +-- ## 7. Which Squadrons will I create and which name will I give each Squadron? +-- +-- The defense system works with Squadrons. Each Squadron must be given a unique name, that forms the **key** to the defense system. +-- Several options and activities can be set per Squadron. +-- +-- ## 8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- +-- Squadrons are placed as the "home base" on an airfield, carrier or farp. +-- Carefully plan where each Squadron will be located as part of the defense system. +-- +-- ## 9. Which plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? +-- +-- Per Squadron, one or multiple plane models can be allocated as **Templates**. +-- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. +-- The A2A defense system will select from the given templates a random template to spawn a new plane (group). +-- +-- ## 10. Which payloads, skills and skins will these plane models have? +-- +-- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, +-- each having different payloads, skills and skins. +-- The A2A defense system will select from the given templates a random template to spawn a new plane (group). +-- +-- ## 11. For each Squadron, which will perform CAP? +-- +-- Per Squadron, evaluate which Squadrons will perform CAP. +-- Not all Squadrons need to perform CAP. +-- +-- ## 12. For each Squadron doing CAP, in which ZONE(s) will the CAP be performed? +-- +-- Per CAP, evaluate **where** the CAP will be performed, in other words, define the **zone**. +-- Near the border or a bit further away? +-- +-- ## 13. For each Squadron doing CAP, which zone types will I create? +-- +-- Per CAP zone, evaluate whether you want: +-- +-- * simple trigger zones +-- * polygon zones +-- * moving zones +-- +-- Depending on the type of zone selected, a different @{Zone} object needs to be created from a ZONE_ class. +-- +-- ## 14. For each Squadron doing CAP, what are the time intervals and CAP amounts to be performed? +-- +-- For each CAP: +-- +-- * **How many** CAP you want to have airborne at the same time? +-- * **How frequent** you want the defense mechanism to check whether to start a new CAP? +-- +-- ## 15. For each Squadron, which will perform GCI? +-- +-- For each Squadron, evaluate which Squadrons will perform GCI? +-- Not all Squadrons need to perform GCI. +-- +-- ## 16. For each Squadron, which takeoff method will I use? +-- +-- For each Squadron, evaluate which takeoff method will be used: +-- +-- * Straight from the air +-- * From the runway +-- * From a parking spot with running engines +-- * From a parking spot with cold engines +-- +-- **The default takeoff method is staight in the air.** +-- +-- ## 17. For each Squadron, which landing method will I use? +-- +-- For each Squadron, evaluate which landing method will be used: +-- +-- * Despawn near the airbase when returning +-- * Despawn after landing on the runway +-- * Despawn after engine shutdown after landing +-- +-- **The default landing method is despawn when near the airbase when returning.** +-- +-- ## 18. For each Squadron, which overhead will I use? +-- +-- For each Squadron, depending on the airplane type (modern, old) and payload, which overhead is required to provide any defense? +-- In other words, if **X** attacker airplanes are detected, how many **Y** defense airplanes need to be spawned per squadron? +-- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. +-- The overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. +-- +-- **The default overhead is 1. A value greater than 1, like 1.5 will increase the overhead with 50%, a value smaller than 1, like 0.5 will decrease the overhead with 50%.** +-- +-- ## 19. For each Squadron, which grouping will I use? +-- +-- When multiple targets are detected, how will defense airplanes be grouped when multiple defense airplanes are spawned for multiple attackers? +-- Per one, two, three, four? +-- +-- **The default grouping is 1. That means, that each spawned defender will act individually.** +-- +-- === -- -- ### Authors: **Sven Van de Velde (FlightControl)** rework of GCICAP + introduction of new concepts (squadrons). -- ### Authors: **Stonehouse**, **SNAFU** in terms of the advice, documentation, and the original GCICAP script. -- --- ### Contributions: --- --- ==== --- -- @module AI_A2A_Dispatcher ---BASE:TraceClass("AI_A2A_DISPATCHER") + do -- AI_A2A_DISPATCHER @@ -27,6 +169,20 @@ do -- AI_A2A_DISPATCHER -- -- The @{#AI_A2A_DISPATCHER} class is designed to create an automatic air defence system for a coalition. -- + -- ==== + -- + -- # Demo Missions + -- + -- ### [AI\_A2A\_DISPATCHER Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching) + -- + -- ==== + -- + -- # YouTube Channel + -- + -- ### [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) + -- + -- === + -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) -- -- It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. @@ -38,13 +194,20 @@ do -- AI_A2A_DISPATCHER -- Note that in order to create a two way A2A defense system, two AI\_A2A\_DISPATCHER defense system may need to be created, for each coalition one. -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. -- + -- === + -- + -- # USAGE GUIDE + -- -- ## 1. AI\_A2A\_DISPATCHER constructor: -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_1.JPG) + -- + -- -- The @{#AI_A2A_DISPATCHER.New}() method creates a new AI\_A2A\_DISPATCHER instance. -- -- ### 1.1. Define the **EWR network**: -- - -- As part of the AI\_A2A\_DISPATCHER constructor, an EWR network must be given as the first parameter. + -- As part of the AI\_A2A\_DISPATCHER :New() constructor, an EWR network must be given as the first parameter. -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia5.JPG) @@ -70,25 +233,34 @@ do -- AI_A2A_DISPATCHER -- -- See the following example to setup an EWR network containing EWR stations and AWACS. -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_2.JPG) + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_3.JPG) + -- -- -- Define a SET_GROUP object that builds a collection of groups that define the EWR network. -- -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR. -- DetectionSetGroup = SET_GROUP:New() -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) -- DetectionSetGroup:FilterStart() -- - -- -- Setup the detection. + -- -- Setup the detection and group targets to a 30km range! -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) -- -- -- Setup the A2A dispatcher, and initialize it. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- + -- -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with **DF CCCP AWACS** or **DF CCCP EWR** to be included in the Set. -- **DetectionSetGroup** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. + -- -- Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is choosen, which is 30km. -- The **Detection** object is then passed to the @{#AI_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A defense detection mechanism. -- - -- ### 2. Define the detected **target grouping radius**: + -- You could build a **mutual defense system** like this: + -- + -- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red ) + -- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue ) + -- + -- ### 2. Define the detected **target grouping radius**: -- -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. @@ -98,29 +270,65 @@ do -- AI_A2A_DISPATCHER -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! -- - -- ## 3. Set the **Engage radius**: + -- ## 3. Set the **Engage Radius**: -- - -- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. + -- Define the **Engage Radius** to **engage any target by airborne friendlies**, + -- which are executing **cap** or **returning** from an intercept mission. -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia10.JPG) -- - -- So, if there is a target area detected and reported, + -- If there is a target area detected and reported, -- then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, - -- will be considered to receive the command to engage that target area. - -- You need to evaluate the value of this parameter carefully. - -- If too small, more intercept missions may be triggered upon detected target areas. - -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. -- - -- ## 4. Set the **Intercept radius** or **Gci radius**: + -- For example, if **50000** or **50km** is given as a value, then any friendly that is airborne within **50km** from the detected target, + -- will be considered to receive the command to engage that target area. + -- + -- You need to evaluate the value of this parameter carefully: + -- + -- * If too small, more intercept missions may be triggered upon detected target areas. + -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. + -- + -- The **default** Engage Radius is defined as **100000** or **100km**. + -- Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to set a specific Engage Radius. + -- **The Engage Radius is defined for ALL squadrons which are operational.** + -- + -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-019%20-%20AI_A2A%20-%20Engage%20Range%20Test) + -- + -- In this example an Engage Radius is set to various values. + -- + -- -- Set 50km as the radius to engage any target by airborne friendlies. + -- A2ADispatcher:SetEngageRadius( 50000 ) + -- + -- -- Set 100km as the radius to engage any target by airborne friendlies. + -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. + -- + -- + -- ## 4. Set the **Ground Controlled Intercept Radius** or **Gci radius**: -- -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. - -- You want it to wait until a certain intercept range is reached, which is the distance of the closest airbase to targer being smaller than the **Gci radius**. - -- The Gci radius is by default defined as 200km. This value can be overridden using the method @{#AI_A2A_DISPATCHER.SetGciRadius}(). - -- Override the default Gci radius when the era of the warfare is early, or, when you don't want to let the AI_A2A_DISPATCHER react immediately when - -- a certain border or area is not being crossed. + -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** + -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. -- + -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, + -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. + -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** + -- + -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-013%20-%20AI_A2A%20-%20Intercept%20Test) + -- + -- In these examples, the Gci Radius is set to various values: + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. + -- A2ADispatcher:SetGciRadius( 100000 ) + -- + -- -- Set 200km as the radius to ground control intercept. + -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. + -- -- ## 5. Set the **borders**: -- -- According to the tactical and strategic design of the mission broadly decide the shape and extent of red and blue territories. @@ -128,7 +336,8 @@ do -- AI_A2A_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia4.JPG) -- - -- Define a border area to simulate a **cold war** scenario and use the method @{#AI_A2A_DISPATCHER.SetBorderZone}() to create a border zone for the dispatcher. + -- **Define a border area to simulate a cold war scenario.** + -- Use the method @{#AI_A2A_DISPATCHER.SetBorderZone}() to create a border zone for the dispatcher. -- -- A **cold war** is one where CAP aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. -- A **hot war** is one where CAP aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send CAP and GCI aircraft to attack it. @@ -140,6 +349,23 @@ do -- AI_A2A_DISPATCHER -- it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. -- In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. -- + -- Demonstration Mission: [AID-009 - AI_A2A - Border Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-009 - AI_A2A - Border Test) + -- + -- In this example a border is set for the CCCP A2A dispatcher: + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_4.JPG) + -- + -- -- Setup the A2A dispatcher, and initialize it. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Setup the border. + -- -- Initialize the dispatcher, setting up a border zone. This is a polygon, + -- -- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area. + -- -- Any enemy crossing this border will be engaged. + -- + -- CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) + -- A2ADispatcher:SetBorderZone( CCCPBorderZone ) + -- -- ## 6. Squadrons: -- -- The AI\_A2A\_DISPATCHER works with **Squadrons**, that need to be defined using the different methods available. @@ -152,7 +378,7 @@ do -- AI_A2A_DISPATCHER -- * Have name (string) that is the identifier or key of the squadron. -- * Have specific plane types. -- * Are located at one airbase. - -- * Have a limited set of resources. + -- * Optionally have a limited set of resources. The default is that squadrons have **unlimited resources**. -- -- The name of the squadron given acts as the **squadron key** in the AI\_A2A\_DISPATCHER:Squadron...() methods. -- @@ -165,6 +391,17 @@ do -- AI_A2A_DISPATCHER -- -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. -- + -- This example defines a couple of squadrons. Note the templates defined within the Mission Editor. + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_5.JPG) + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_6.JPG) + -- + -- -- Setup the squadrons. + -- A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 ) + -- A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 ) + -- A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 ) + -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 ) + -- A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 ) -- -- ### 6.1. Set squadron take-off methods -- @@ -176,6 +413,8 @@ do -- AI_A2A_DISPATCHER -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. -- + -- **The default landing method is to spawn new aircraft directly in the air.** + -- -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: -- @@ -187,6 +426,31 @@ do -- AI_A2A_DISPATCHER -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! -- + -- This example sets the default takeoff method to be from the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Takeoff methods + -- + -- -- The default takeoff + -- A2ADispatcher:SetDefaultTakeOffFromRunway() + -- + -- -- The individual takeoff per squadron + -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air ) + -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) + -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- + -- + -- ### 6.1. Set Squadron takeoff altitude when spawning new aircraft in the air. + -- + -- In the case of the @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. + -- That is modifying or setting the **altitude** from where planes spawn in the air. + -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. + -- The default takeoff altitude can be modified or set using the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). + -- As part of the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. + -- If this parameter is not specified, then the default altitude will be used for the squadron. + -- -- ### 6.2. Set squadron landing methods -- -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: @@ -202,9 +466,25 @@ do -- AI_A2A_DISPATCHER -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. -- + -- This example defines the default landing method to be at the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Landing methods + -- + -- -- The default landing method + -- A2ADispatcher:SetDefaultLandingAtRunway() + -- + -- -- The individual landing per squadron + -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) + -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) + -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway ) + -- + -- -- ### 6.3. Set squadron grouping -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() to set the amount of CAP or GCI flights that will take-off when spawned. + -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() to set the grouping of CAP or GCI flights that will take-off when spawned. -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia12.JPG) -- @@ -213,9 +493,11 @@ do -- AI_A2A_DISPATCHER -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by CAP or any airborne flight, -- a GCI needs to be started, the GCI flights will be grouped as follows: Group 1 of 2 flights and Group 2 of one flight! -- + -- Even more ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! + -- -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. -- - -- ### 6.4. Balance or setup effectiveness of the air defenses in case of GCI + -- ### 6.4. Overhead and Balance the effectiveness of the air defenses in case of GCI. -- -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. @@ -224,7 +506,7 @@ do -- AI_A2A_DISPATCHER -- -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. -- - -- The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, + -- The @{#AI_A2A_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. -- -- For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... @@ -237,8 +519,18 @@ do -- AI_A2A_DISPATCHER -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group -- multiplied by the Overhead and rounded up to the smallest integer. -- + -- For example ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! + -- -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. -- + -- ## 6.5. Squadron fuel treshold. + -- + -- When an airplane gets **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- - The defender will go RTB, and will be replaced with a new defender if possible. + -- - The defender will refuel at a tanker, if a tanker has been specified for the squadron. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of spawned airplanes for all squadrons. + -- -- ## 7. Setup a squadron for CAP -- -- ### 7.1. Set the CAP zones @@ -269,14 +561,20 @@ do -- AI_A2A_DISPATCHER -- -- The following example illustrates how CAP zones are coded: -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_8.JPG) + -- -- -- CAP Squadron execution. -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_7.JPG) -- -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_9.JPG) -- -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) @@ -304,9 +602,34 @@ do -- AI_A2A_DISPATCHER -- -- For example, the following setup will create a CAP for squadron "Sochi": -- - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- + -- ## 7.3. Squadron tanker to refuel when executing CAP and defender is out of fuel. + -- + -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. + -- This greatly increases the efficiency of your CAP operations. + -- + -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. + -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the default tanker for the refuelling. + -- You can also specify a specific tanker for refuelling for a squadron by using the method @{#AI_A2A_DISPATCHER.SetSquadronTanker}(). + -- + -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. + -- + -- For example, the following setup will create a CAP for squadron "Gelend" with a refuel task for the squadron: + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_10.JPG) + -- + -- -- Define the CAP + -- A2ADispatcher:SetSquadron( "Gelend", AIRBASE.Caucasus.Gelendzhik, { "SQ CCCP SU-30" }, 20 ) + -- A2ADispatcher:SetSquadronCap( "Gelend", ZONE:New( "PatrolZoneGelend" ), 4000, 8000, 600, 800, 1000, 1300 ) + -- A2ADispatcher:SetSquadronCapInterval( "Gelend", 2, 30, 600, 1 ) + -- A2ADispatcher:SetSquadronGci( "Gelend", 900, 1200 ) + -- + -- -- Setup the Refuelling for squadron "Gelend", at tanker (group) "TankerGelend" when the fuel in the tank of the CAP defenders is less than 80%. + -- A2ADispatcher:SetSquadronFuelThreshold( "Gelend", 0.8 ) + -- A2ADispatcher:SetSquadronTanker( "Gelend", "TankerGelend" ) + -- -- ## 8. Setup a squadron for GCI: -- -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. @@ -322,7 +645,7 @@ do -- AI_A2A_DISPATCHER -- -- For example, the following setup will create a GCI for squadron "Sochi": -- - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) + -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) -- -- ## 9. Other configuration options -- @@ -332,180 +655,135 @@ do -- AI_A2A_DISPATCHER -- Use the method @{#AI_A2A_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. -- Note that there may be some performance impact if this panel is shown. -- - -- ## 10. Mission Editor Guide: + -- ## 10. Defaults settings. -- - -- The following steps need to be followed, in order to setup the different borders, templates and groups within the mission editor: + -- This provides a good overview of the different parameters that are setup or hardcoded by default. + -- For some default settings, a method is available that allows you to tweak the defaults. -- - -- ### 10.1. Define your EWR network: + -- ## 10.1. Default takeoff method. -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia14.JPG) + -- The default **takeoff method** is set to **in the air**, which means that new spawned airplanes will be spawned directly in the air above the airbase by default. -- - -- At strategic positions within the battlefield, position the correct groups of units that have radar detection capability in the battlefield. - -- Create the naming of these groups as such, that these can be easily recognized and included as a prefix within your lua MOOSE mission script. - -- These prefixes should be unique, so that accidentally no other groups would be incorporated within the EWR network. + -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** -- - -- ### 10.2. Define the border zone: + -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. + -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia15.JPG) + -- ## 10.2. Default landing method. -- - -- For a cold war situation, define your border zone. - -- You can do this in many ways, as the @{Zone} capability within MOOSE outlines. However, the best practice is to create a ZONE_POLYGON class. - -- To do this, you need to create a zone using a helicopter group, that is late activated, and has a unique group name. - -- Place the helicopter where the border zone should start, and draw using the waypoints the polygon zone around the area that is considered the border. - -- The helicopter group name is included as the reference within your lua MOOSE mission script, so ensure that the name is unique and is easily recognizable. + -- The default **landing method** is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. -- - -- ### 10.3. Define the plane templates: + -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia16.JPG) + -- * @{#AI_A2A_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. + -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. + -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- Define the templates of the planes that define the format of planes that will take part in the A2A defenses of your coalition. - -- These plane templates will never be activated, but are used to create a diverse airplane portfolio allocated to your squadrons. + -- ## 10.3. Default overhead. -- - -- IMPORTANT! **Plane templates MUST be of ONE unit, and must have the Late Activated flag switched on!** + -- The default **overhead** is set to **1**. That essentially means that there isn't any overhead set by default. -- - -- Plane templates are used to diversify the defending squadrons with: + -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. + -- + -- Use the @{#AI_A2A_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. + -- + -- ## 10.4. Default grouping. -- - -- * different airplane types - -- * different airplane skins - -- * different skill levels - -- * different weapon payloads - -- * different fuel and other characteristics + -- The default **grouping** is set to **one airplane**. That essentially means that there won't be any grouping applied by default. + -- + -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. + -- + -- ## 10.5. Default RTB fuel treshold. + -- + -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.6. Default RTB damage treshold. + -- + -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.7. Default settings for CAP. + -- + -- ### 10.7.1. Default CAP Time Interval. + -- + -- CAP is time driven, and will evaluate in random time intervals if a new CAP needs to be spawned. + -- The **default CAP time interval** is between **180** and **600** seconds. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. + -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. + -- + -- ### 10.7.2. Default CAP limit. + -- + -- Multiple CAP can be airborne at the same time for one squadron, which is controlled by the **CAP limit**. + -- The **default CAP limit** is 1 CAP per squadron to be airborne at the same time. + -- Note that the default CAP limit is used when a Squadron CAP is defined, and cannot be changed afterwards. + -- So, ensure that you set the default CAP limit **before** you spawn the Squadron CAP. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. + -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. + -- + -- ## 10.7.3. Default tanker for refuelling when executing CAP. + -- + -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. + -- This greatly increases the efficiency of your CAP operations. + -- + -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. + -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelTreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- + -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. + -- + -- For example, the following setup will set the default refuel tanker to "Tanker": + -- + -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_11.JPG) + -- + -- -- Define the CAP + -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) + -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) + -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) + -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) + -- + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) + -- A2ADispatcher:SetDefaultTanker( "Tanker" ) + -- + -- ## 10.8. Default settings for GCI. + -- + -- ## 10.8.1. Optimal intercept point calculation. + -- + -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. + -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. + -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. + -- + -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: + -- + -- * The average bearing of the intruders for an amount of seconds. + -- * The average speed of the intruders for an amount of seconds. + -- * An assumed time it takes to get planes operational at the airbase. + -- + -- The **intercept point** will determine: + -- + -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. + -- * The optimal airbase from where defenders will takeoff for GCI. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. + -- + -- ## 10.8.2. Default Disengage Radius. + -- + -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. + -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! -- - -- Place these airplane templates are good visible locations within your mission, so you can easily retrieve them back. + -- Use the method @{#AI_A2A_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. -- - -- ### 10.4. Define the CAP zones: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia17.JPG) - -- - -- Similar as with the border zone, define the CAP zones using helicopter group templates. Its waypoints define the polygon zones. - -- But you can also define other zone types instead, like moving zones. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia18.JPG) - -- - -- Or you can define also zones using trigger zones. - -- - -- ### 10.5. "Script it": - -- - -- Find the following mission script as an example: - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the EWR network. - -- -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Here we define detection to be done by area, with a grouping radius of 3000. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- - -- - -- -- Initialize the dispatcher, setting up a border zone. This is a polygon, - -- -- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area. - -- -- Any enemy crossing this border will be engaged. - -- CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) - -- A2ADispatcher:SetBorderZone( { CCCPBorderZone } ) - -- - -- -- Initialize the dispatcher, setting up a radius of 100km where any airborne friendly - -- -- without an assignment within 100km radius from a detected target, will engage that target. - -- A2ADispatcher:SetEngageRadius( 300000 ) - -- - -- -- Setup the squadrons. - -- A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 ) - -- - -- -- Setup the overhead - -- A2ADispatcher:SetSquadronOverhead( "Mineralnye", 1.2 ) - -- A2ADispatcher:SetSquadronOverhead( "Maykop", 1 ) - -- A2ADispatcher:SetSquadronOverhead( "Mozdok", 1.5 ) - -- A2ADispatcher:SetSquadronOverhead( "Sochi", 1 ) - -- A2ADispatcher:SetSquadronOverhead( "Novo", 1 ) - -- - -- -- Setup the Grouping - -- A2ADispatcher:SetSquadronGrouping( "Mineralnye", 2 ) - -- A2ADispatcher:SetSquadronGrouping( "Sochi", 2 ) - -- A2ADispatcher:SetSquadronGrouping( "Novo", 3 ) - -- - -- -- Setup the Takeoff methods - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- -- Setup the Landing methods - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway ) - -- - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) - -- - -- #### 10.5.1. Script the EWR network - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia20.JPG) - -- - -- #### 10.5.2. Script the AI\_A2A\_DISPATCHER object and configure it - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia21.JPG) - -- - -- #### 10.5.3. Script the squadrons - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia22.JPG) - -- - -- Create the squadrons using the @{#AI_A2A_DISPATCHER.SetSquadron)() method. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia23.JPG) - -- - -- Define the defense overhead of the squadrons using the @{#AI_A2A_DISPATCHER.SetSquadronOverhead)() method. - -- Group the squadron units using the @{#AI_A2A_DISPATCHER.SetSquadronGrouping)() method. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia24.JPG) - -- - -- Set the takeoff method of the squadron using the @{#AI_A2A_DISPATCHER.SetSquadronTakeoff)() methods. - -- Set the landing method of the squadron using the @{#AI_A2A_DISPATCHER.SetSquadronLanding)() methods. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia25.JPG) - -- - -- Create the @{Zone} objects using: - -- - -- * @{Zone#ZONE} class to create a zone using a trigger zone set in the mission editor. - -- * @{Zone#ZONE_UNIT} class to create a zone around a unit object. - -- * @{Zone#ZONE_GROUP} class to create a zone around a group object. - -- * @{Zone#ZONE_POLYGON} class to create a polygon zone using a late activated group object. - -- - -- Use the @{#AI_A2A_DISPATCHER.SetSquadronCap)() method to define CAP execution for the squadron, within the CAP zone defined. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia26.JPG) - -- - -- Use the @{#AI_A2A_DISPATCHER.SetSquadronCapInterval)() method to define how many CAP groups can be airborne at the same time, and the timing intervals. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia27.JPG) - -- - -- Use the @{#AI_A2A_DISPATCHER.SetSquadronGci)() method to define GCI execution for the squadron. -- -- ## 11. Q & A: -- @@ -549,15 +827,26 @@ do -- AI_A2A_DISPATCHER } --- AI_A2A_DISPATCHER constructor. + -- This is defining the A2A DISPATCHER for one coaliton. + -- The Dispatcher works with a @{Functional#Detection} object that is taking of the detection of targets using the EWR units. + -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. - -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. -- @return #AI_A2A_DISPATCHER self -- @usage -- - -- -- Set a new AI A2A Dispatcher object, based on an EWR network with a 6 km grouping radius. + -- -- Setup the Detection, using DETECTION_AREAS. + -- -- First define the SET of GROUPs that are defining the EWR network. + -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. + -- DetectionSetGroup = SET_GROUP:New() + -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) + -- DetectionSetGroup:FilterStart() -- + -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- -- function AI_A2A_DISPATCHER:New( Detection ) @@ -570,14 +859,28 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons = {} -- The Defender Squadrons. self.DefenderSpawns = {} self.DefenderTasks = {} -- The Defenders Tasks. + self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. -- TODO: Check detection through radar. - self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) + self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) --self.Detection:InitDetectRadar( true ) - self.Detection:SetDetectionInterval( 30 ) + self.Detection:SetRefreshTimeInterval( 30 ) self:SetEngageRadius() self:SetGciRadius() + self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. + self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. + + self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) + self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. + self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) + self:SetDefaultOverhead( 1 ) + self:SetDefaultGrouping( 1 ) + self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. + self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. + self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. + self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron. + self:AddTransition( "Started", "Assign", "Started" ) @@ -675,6 +978,7 @@ do -- AI_A2A_DISPATCHER -- This will avoid the detection to still "know" the shot unit until the next detection. -- Otherwise, a new intercept or engage may happen for an already shot plane! + self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) @@ -697,6 +1001,7 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2A_DISPATCHER:OnEventLand( EventData ) + self:E( "Landed" ) local DefenderUnit = EventData.IniUnit local Defender = EventData.IniGroup local Squadron = self:GetSquadronFromDefender( Defender ) @@ -715,7 +1020,11 @@ do -- AI_A2A_DISPATCHER -- Damaged units cannot be repaired anymore. DefenderUnit:Destroy() return - end + end + if DefenderUnit:GetFuel() <= self.DefenderDefault.FuelThreshold then + DefenderUnit:Destroy() + return + end end end @@ -739,51 +1048,85 @@ do -- AI_A2A_DISPATCHER end --- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, + -- If there is a target area detected and reported, then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). + -- -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -- will be considered to receive the command to engage that target area. - -- You need to evaluate the value of this parameter carefully. - -- If too small, more intercept missions may be triggered upon detected target areas. - -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. + -- + -- You need to evaluate the value of this parameter carefully: + -- + -- * If too small, more intercept missions may be triggered upon detected target areas. + -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. + -- + -- **Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to modify the default Engage Radius for ALL squadrons.** + -- + -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-019%20-%20AI_A2A%20-%20Engage%20Range%20Test) + -- -- @param #AI_A2A_DISPATCHER self -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. -- @return #AI_A2A_DISPATCHER -- @usage -- -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- Dispatcher:SetEngageRadius( 50000 ) + -- A2ADispatcher:SetEngageRadius( 50000 ) -- -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- Dispatcher:SetEngageRadius() -- 100000 is the default value. + -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. -- function AI_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) - self.Detection:SetFriendliesRange( EngageRadius ) + self.Detection:SetFriendliesRange( EngageRadius or 100000 ) + + return self + end + + --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. + -- @param #AI_A2A_DISPATCHER self + -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Set 50km as the Disengage Radius. + -- A2ADispatcher:SetDisengageRadius( 50000 ) + -- + -- -- Set 100km as the Disengage Radius. + -- A2ADispatcher:SetDisngageRadius() -- 300000 is the default value. + -- + function AI_A2A_DISPATCHER:SetDisengageRadius( DisengageRadius ) + + self.DisengageRadius = DisengageRadius or 300000 return self end + --- Define the radius to check if a target can be engaged by an ground controlled intercept. - -- So, if there is a target area detected and reported, - -- and a GCI is to be executed, - -- then it will be check if the target is within the GCI from the nearest airbase. - -- For example, if 150000 is given as a value, then any airbase within 150km from the detected target, - -- will be considered to receive the command to GCI. - -- You need to evaluate the value of this parameter carefully. - -- If too small, intercept missions may be triggered too late. - -- If too large, intercept missions may be triggered when the detected target is too far. + -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. + -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** + -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. + -- + -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, + -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. + -- + -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. + -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** + -- + -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-013%20-%20AI_A2A%20-%20Intercept%20Test) + -- -- @param #AI_A2A_DISPATCHER self -- @param #number GciRadius (Optional, Default = 200000) The radius to ground control intercept detected targets from the nearest airbase. -- @return #AI_A2A_DISPATCHER -- @usage -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. - -- Dispatcher:SetGciRadius( 100000 ) + -- A2ADispatcher:SetGciRadius( 100000 ) -- -- -- Set 200km as the radius to ground control intercept. - -- Dispatcher:SetGciRadius() -- 200000 is the default value. + -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. -- function AI_A2A_DISPATCHER:SetGciRadius( GciRadius ) @@ -800,13 +1143,24 @@ do -- AI_A2A_DISPATCHER -- If it’s a cold war then the **borders of red and blue territory** need to be defined using a @{zone} object derived from @{Zone#ZONE_BASE}. This method needs to be used for this. -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 -- @param #AI_A2A_DISPATCHER self - -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, that defines a zone between + -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- Set a polygon zone as the border for the A2A dispatcher. + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Set one ZONE_POLYGON object as the border for the A2A dispatcher. -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- Dispatcher:SetBorderZone( BorderZone ) + -- A2ADispatcher:SetBorderZone( BorderZone ) + -- + -- or + -- + -- -- Set two ZONE_POLYGON objects as the border for the A2A dispatcher. + -- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit. + -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. + -- A2ADispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) + -- -- function AI_A2A_DISPATCHER:SetBorderZone( BorderZone ) @@ -825,6 +1179,14 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the Tactical Display for debug mode. + -- A2ADispatcher:SetTacticalDisplay( true ) + -- function AI_A2A_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) self.TacticalDisplay = TacticalDisplay @@ -832,6 +1194,83 @@ do -- AI_A2A_DISPATCHER return self end + + --- Set the default damage treshold when defenders will RTB. + -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + -- @param #AI_A2A_DISPATCHER self + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default damage treshold. + -- A2ADispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. + -- + function AI_A2A_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) + + self.DefenderDefault.DamageThreshold = DamageThreshold + + return self + end + + + --- Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing. + -- The default CAP time interval is between 180 and 600 seconds. + -- @param #AI_A2A_DISPATCHER self + -- @param #number CapMinSeconds The minimum amount of seconds for the random time interval. + -- @param #number CapMaxSeconds The maximum amount of seconds for the random time interval. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default CAP time interval. + -- A2ADispatcher:SetDefaultCapTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. + -- + function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval( CapMinSeconds, CapMaxSeconds ) + + self.DefenderDefault.CapMinSeconds = CapMinSeconds + self.DefenderDefault.CapMaxSeconds = CapMaxSeconds + + return self + end + + + --- Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron. + -- The default CAP limit is 1 CAP, which means one CAP group being spawned. + -- @param #AI_A2A_DISPATCHER self + -- @param #number CapLimit The maximum amount of CAP that can be airborne at the same time for the squadron. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default CAP limit. + -- A2ADispatcher:SetDefaultCapLimit( 2 ) -- Maximum 2 CAP per squadron. + -- + function AI_A2A_DISPATCHER:SetDefaultCapLimit( CapLimit ) + + self.DefenderDefault.CapLimit = CapLimit + + return self + end + + + function AI_A2A_DISPATCHER:SetIntercept( InterceptDelay ) + + self.DefenderDefault.InterceptDelay = InterceptDelay + + local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS + Detection:SetIntercept( true, InterceptDelay ) + + return self + end + + --- Calculates which AI friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self -- @param DetectedItem @@ -866,6 +1305,12 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:GetDefenderTaskTarget( Defender ) return self:GetDefenderTask( Defender ).Target end + + --- + -- @param #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName( Defender ) + return self:GetDefenderTask( Defender ).SquadronName + end --- -- @param #AI_A2A_DISPATCHER self @@ -914,11 +1359,14 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefenderTask( Defender, Type, Fsm, Target ) + function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) + + self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } ) self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} self.DefenderTasks[Defender].Type = Type self.DefenderTasks[Defender].Fsm = Fsm + self.DefenderTasks[Defender].SquadronName = SquadronName if Target then self:SetDefenderTaskTarget( Defender, Target ) @@ -930,25 +1378,91 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self -- @param Wrapper.Group#GROUP AIGroup - function AI_A2A_DISPATCHER:SetDefenderTaskTarget( Defender, Target ) + function AI_A2A_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection ) local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " Message = Message .. Defender:GetName() - Message = Message .. ( Target and ( " target " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" - self:F( { Target = Message } ) - if Target then - self.DefenderTasks[Defender].Target = Target + Message = Message .. ( AttackerDetection and ( " target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]" ) ) or "" + self:F( { AttackerDetection = Message } ) + if AttackerDetection then + self.DefenderTasks[Defender].Target = AttackerDetection end return self end - --- + --- This is the main method to define Squadrons programmatically. + -- Squadrons: + -- + -- * Have a **name or key** that is the identifier or key of the squadron. + -- * Have **specific plane types** defined by **templates**. + -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. + -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. + -- + -- The name of the squadron given acts as the **squadron key** in the AI\_A2A\_DISPATCHER:Squadron...() methods. + -- + -- Additionally, squadrons have specific configuration options to: + -- + -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). + -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). + -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. + -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. + -- + -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. + -- -- @param #AI_A2A_DISPATCHER self + -- + -- @param #string SquadronName A string (text) that defines the squadron identifier or the key of the Squadron. + -- It can be any name, for example `"104th Squadron"` or `"SQ SQUADRON1"`, whatever. + -- As long as you remember that this name becomes the identifier of your squadron you have defined. + -- You need to use this name in other methods too! + -- + -- @param #string AirbaseName The airbase name where you want to have the squadron located. + -- You need to specify here EXACTLY the name of the airbase as you see it in the mission editor. + -- Examples are `"Batumi"` or `"Tbilisi-Lochini"`. + -- EXACTLY the airbase name, between quotes `""`. + -- To ease the airbase naming when using the LDT editor and IntelliSense, the @{Airbase#AIRBASE} class contains enumerations of the airbases of each map. + -- + -- * Caucasus: @{Airbase#AIRBASE.Caucaus} + -- * Nevada or NTTR: @{Airbase#AIRBASE.Nevada} + -- * Normandy: @{Airbase#AIRBASE.Normandy} + -- + -- @param #string TemplatePrefixes A string or an array of strings specifying the **prefix names of the templates** (not going to explain what is templates here again). + -- Examples are `{ "104th", "105th" }` or `"104th"` or `"Template 1"` or `"BLUE PLANES"`. + -- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. + -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. + -- + -- @param #number Resources (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. + -- + -- @usage + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- @usage + -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... + -- A2ADispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) + -- + -- @usage + -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... + -- -- Note that in this implementation, the A2A dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. + -- -- Note the usage of the {} for the airplane templates list. + -- A2ADispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) + -- + -- @usage + -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... + -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) + -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) + -- + -- @usage + -- -- This is an example like the previous, but now with infinite resources. + -- -- The Resources parameter is not given in the SetSquadron method. + -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) + -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) + -- + -- -- @return #AI_A2A_DISPATCHER - function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, SpawnTemplates, Resources ) + function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, Resources ) - self:E( { SquadronName = SquadronName, AirbaseName = AirbaseName, SpawnTemplates = SpawnTemplates, Resources = Resources } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -956,24 +1470,26 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.Name = SquadronName DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) + if not DefenderSquadron.Airbase then + error( "Cannot find airbase with name:" .. AirbaseName ) + end DefenderSquadron.Spawn = {} - if type( SpawnTemplates ) == "string" then - local SpawnTemplate = SpawnTemplates + if type( TemplatePrefixes ) == "string" then + local SpawnTemplate = TemplatePrefixes self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] else - for TemplateID, SpawnTemplate in pairs( SpawnTemplates ) do + for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] end end DefenderSquadron.Resources = Resources - - self:SetSquadronOverhead( SquadronName, 1 ) - self:SetSquadronTakeoffInAir( SquadronName ) - self:SetSquadronLandingNearAirbase(SquadronName) + DefenderSquadron.TemplatePrefixes = TemplatePrefixes + self:E( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, Resources } } ) + return self end @@ -991,7 +1507,7 @@ do -- AI_A2A_DISPATCHER end - --- + --- Set a CAP for a Squadron. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. @@ -1036,14 +1552,28 @@ do -- AI_A2A_DISPATCHER Cap.EngageMaxSpeed = EngageMaxSpeed Cap.AltType = AltType - self:SetSquadronCapInterval( SquadronName, 2, 180, 600, 1 ) + self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) + + self:E( { CAP = { SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType } } ) + + -- Add the CAP to the EWR network. + + local RecceSet = self.Detection:GetDetectionSetGroup() + RecceSet:FilterPrefixes( DefenderSquadron.TemplatePrefixes ) + RecceSet:FilterStart() + + self.Detection:SetFriendlyPrefixes( DefenderSquadron.TemplatePrefixes ) return self end - --- + --- Set the squadron CAP parameters. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. + -- @param #number CapLimit (optional) The maximum amount of CAP groups to be spawned. Note that a CAP is a group, so can consist out of 1 to 4 airplanes. The default is 1 CAP group. + -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new CAP will be spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new CAP will be spawned. The default is 600 seconds. + -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2A_DISPATCHER -- @usage -- @@ -1069,16 +1599,23 @@ do -- AI_A2A_DISPATCHER local Cap = self.DefenderSquadrons[SquadronName].Cap if Cap then - Cap.LowInterval = LowInterval or 300 + Cap.LowInterval = LowInterval or 180 Cap.HighInterval = HighInterval or 600 Cap.Probability = Probability or 1 - Cap.CapLimit = CapLimit + Cap.CapLimit = CapLimit or 1 Cap.Scheduler = Cap.Scheduler or SCHEDULER:New( self ) local Scheduler = Cap.Scheduler -- Core.Scheduler#SCHEDULER + local ScheduleID = Cap.ScheduleID local Variance = ( Cap.HighInterval - Cap.LowInterval ) / 2 - local Median = Cap.LowInterval + Variance - local Randomization = Variance / Median - Scheduler:Schedule(self, self.SchedulerCAP, { SquadronName }, Median, Median, Randomization ) + local Repeat = Cap.LowInterval + Variance + local Randomization = Variance / Repeat + local Start = math.random( 1, Cap.HighInterval ) + + if ScheduleID then + Scheduler:Stop( ScheduleID ) + end + + Cap.ScheduleID = Scheduler:Schedule( self, self.SchedulerCAP, { SquadronName }, Start, Repeat, Randomization ) else error( "This squadron does not exist:" .. SquadronName ) end @@ -1116,11 +1653,12 @@ do -- AI_A2A_DISPATCHER local DefenderSquadron = self:GetSquadron( SquadronName ) - if DefenderSquadron.Resources > 0 then + if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then local Cap = DefenderSquadron.Cap if Cap then local CapCount = self:CountCapAirborne( SquadronName ) + self:E( { CapCount = CapCount } ) if CapCount < Cap.CapLimit then local Probability = math.random() if Probability <= Cap.Probability then @@ -1141,11 +1679,11 @@ do -- AI_A2A_DISPATCHER self:F({SquadronName = SquadronName}) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} + self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} local DefenderSquadron = self:GetSquadron( SquadronName ) - if DefenderSquadron.Resources > 0 then + if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then local Gci = DefenderSquadron.Gci if Gci then return DefenderSquadron @@ -1177,8 +1715,48 @@ do -- AI_A2A_DISPATCHER Intercept.Name = SquadronName Intercept.EngageMinSpeed = EngageMinSpeed Intercept.EngageMaxSpeed = EngageMaxSpeed + + self:E( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed } } ) end + --- Defines the default amount of extra planes that will take-off as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, + -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... + -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: + -- + -- * Higher than 1, will increase the defense unit amounts. + -- * Lower than 1, will decrease the defense unit amounts. + -- + -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group + -- multiplied by the Overhead and rounded up to the smallest integer. + -- + -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. + -- + -- See example below. + -- + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. + -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. + -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. + -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. + -- + -- A2ADispatcher:SetDefaultOverhead( 1.5 ) + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultOverhead( Overhead ) + + self.DefenderDefault.Overhead = Overhead + + return self + end + + --- Defines the amount of extra planes that will take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -1200,15 +1778,14 @@ do -- AI_A2A_DISPATCHER -- -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. -- - -- Dispatcher:SetSquadronOverhead( 1,5 ) - -- + -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) -- -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) @@ -1219,14 +1796,39 @@ do -- AI_A2A_DISPATCHER return self end - --- + + --- Sets the default grouping of new airplanes spawned. + -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. + -- @param #AI_A2A_DISPATCHER self + -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Set a grouping by default per 2 airplanes. + -- A2ADispatcher:SetDefaultGrouping( 2 ) + -- + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultGrouping( Grouping ) + + self.DefenderDefault.Grouping = Grouping + + return self + end + + + --- Sets the grouping of new airplanes spawned. + -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) - -- Dispatcher:SetSquadronGrouping( "SquadronName", 2 ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Set a grouping per 2 airplanes. + -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- -- -- @return #AI_A2A_DISPATCHER @@ -1238,25 +1840,55 @@ do -- AI_A2A_DISPATCHER return self end + + --- Defines the default method at which new flights will spawn and take-off as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default take-off in the air. + -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Air ) + -- + -- -- Let new flights by default take-off from the runway. + -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Runway ) + -- + -- -- Let new flights by default take-off from the airbase hot. + -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Hot ) + -- + -- -- Let new flights by default take-off from the airbase cold. + -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) + -- + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetDefaultTakeoff( Takeoff ) + + self.DefenderDefault.Takeoff = Takeoff + + return self + end + --- Defines the method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air ) + -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air ) -- -- -- Let new flights take-off from the runway. - -- Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway ) + -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway ) -- -- -- Let new flights take-off from the airbase hot. - -- Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot ) + -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot ) -- -- -- Let new flights take-off from the airbase cold. - -- Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) + -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) -- -- -- @return #AI_A2A_DISPATCHER @@ -1270,16 +1902,34 @@ do -- AI_A2A_DISPATCHER end + --- Gets the default method at which new flights will spawn and take-off as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default take-off in the air. + -- local TakeoffMethod = A2ADispatcher:GetDefaultTakeoff() + -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then + -- ... + -- end + -- + function AI_A2A_DISPATCHER:GetDefaultTakeoff( ) + + return self.DefenderDefault.Takeoff + end + --- Gets the method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- local TakeoffMethod = Dispatcher:GetSquadronTakeoff( "SquadronName" ) + -- local TakeoffMethod = A2ADispatcher:GetSquadronTakeoff( "SquadronName" ) -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then -- ... -- end @@ -1287,39 +1937,82 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:GetSquadronTakeoff( SquadronName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Takeoff + return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff end - --- Sets flights to take-off in the air, as part of the defense system. + --- Sets flights to default take-off in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronTakeoffInAir( "SquadronName" ) + -- -- Let new flights by default take-off in the air. + -- A2ADispatcher:SetDefaultTakeoffInAir() -- -- @return #AI_A2A_DISPATCHER -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName ) + function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() - self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Air ) + self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) return self end - + + --- Sets flights to take-off in the air, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights take-off in the air. + -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) + + self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Air ) + + if TakeoffAltitude then + self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) + end + + return self + end + + + --- Sets flights by default to take-off from the runway, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default take-off from the runway. + -- A2ADispatcher:SetDefaultTakeoffFromRunway() + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() + + self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Runway ) + + return self + end + + --- Sets flights to take-off from the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) + -- -- Let new flights take-off from the runway. + -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER -- @@ -1331,15 +2024,33 @@ do -- AI_A2A_DISPATCHER end + --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default take-off at a hot parking spot. + -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() + + self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Hot ) + + return self + end + --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER -- @@ -1350,15 +2061,35 @@ do -- AI_A2A_DISPATCHER return self end + + --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights take-off from a cold parking spot. + -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() + + self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Cold ) + + return self + end + + --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) + -- -- Let new flights take-off from a cold parking spot. + -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER -- @@ -1370,22 +2101,88 @@ do -- AI_A2A_DISPATCHER end + --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + -- @param #AI_A2A_DISPATCHER self + -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Set the default takeoff altitude when taking off in the air. + -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) + + self.DefenderDefault.TakeoffAltitude = TakeoffAltitude + + return self + end + + --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Set the default takeoff altitude when taking off in the air. + -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. + -- + -- @return #AI_A2A_DISPATCHER + -- + function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.TakeoffAltitude = TakeoffAltitude + + return self + end + + + --- Defines the default method at which flights will land and despawn as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default despawn near the airbase when returning. + -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) + -- + -- -- Let new flights by default despawn after landing land at the runway. + -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtRunway ) + -- + -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. + -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultLanding( Landing ) + + self.DefenderDefault.Landing = Landing + + return self + end + + --- Defines the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. - -- Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) + -- -- Let new flights despawn near the airbase when returning. + -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) -- - -- -- Let new flights take-off from the runway. - -- Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway ) + -- -- Let new flights despawn after landing land at the runway. + -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway ) -- - -- -- Let new flights take-off from the airbase hot. - -- Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) + -- -- Let new flights despawn after landing and parking, and after engine shutdown. + -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) @@ -1397,16 +2194,35 @@ do -- AI_A2A_DISPATCHER end + --- Gets the default method at which flights will land and despawn as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let new flights by default despawn near the airbase when returning. + -- local LandingMethod = A2ADispatcher:GetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) + -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then + -- ... + -- end + -- + function AI_A2A_DISPATCHER:GetDefaultLanding() + + return self.DefenderDefault.Landing + end + + --- Gets the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. - -- local LandingMethod = Dispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) + -- -- Let new flights despawn near the airbase when returning. + -- local LandingMethod = A2ADispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then -- ... -- end @@ -1414,7 +2230,25 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:GetSquadronLanding( SquadronName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Landing + return DefenderSquadron.Landing or self.DefenderDefault.Landing + end + + + --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let flights by default to land near the airbase and despawn. + -- A2ADispatcher:SetDefaultLandingNearAirbase() + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() + + self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) + + return self end @@ -1423,10 +2257,10 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- - -- -- Let flights land in the air and despawn. - -- Dispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) + -- -- Let flights to land near the airbase and despawn. + -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) @@ -1437,15 +2271,33 @@ do -- AI_A2A_DISPATCHER end + --- Sets flights by default to land and despawn at the runway, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let flights by default land at the runway and despawn. + -- A2ADispatcher:SetDefaultLandingAtRunway() + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() + + self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) + + return self + end + + --- Sets flights to land and despawn at the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- Let flights land at the runway and despawn. - -- Dispatcher:SetSquadronLandingAtRunway( "SquadronName" ) + -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) @@ -1456,15 +2308,33 @@ do -- AI_A2A_DISPATCHER end + --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. + -- @param #AI_A2A_DISPATCHER self + -- @usage: + -- + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) + -- + -- -- Let flights by default land and despawn at engine shutdown. + -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() + + self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) + + return self + end + + --- Sets flights to land and despawn at engine shutdown, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local Dispatcher = AI_A2A_DISPATCHER:New( ... ) + -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) -- -- -- Let flights land and despawn at engine shutdown. - -- Dispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) + -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) -- -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) @@ -1474,21 +2344,115 @@ do -- AI_A2A_DISPATCHER return self end + --- Set the default fuel treshold when defenders will RTB or Refuel in the air. + -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + -- @param #AI_A2A_DISPATCHER self + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default fuel treshold. + -- A2ADispatcher:SetDefaultRefuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- + function AI_A2A_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) + + self.DefenderDefault.FuelThreshold = FuelThreshold + + return self + end + + + --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default fuel treshold. + -- A2ADispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- + function AI_A2A_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.FuelThreshold = FuelThreshold + + return self + end + + --- Set the default tanker where defenders will Refuel in the air. + -- @param #AI_A2A_DISPATCHER self + -- @param #strig TankerName A string defining the group name of the Tanker as defined within the Mission Editor. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the default fuel treshold. + -- A2ADispatcher:SetDefaultRefuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- + -- -- Now Setup the default tanker. + -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + function AI_A2A_DISPATCHER:SetDefaultTanker( TankerName ) + + self.DefenderDefault.TankerName = TankerName + + return self + end + + + --- Set the squadron tanker where defenders will Refuel in the air. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #strig TankerName A string defining the group name of the Tanker as defined within the Mission Editor. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Now Setup the squadron fuel treshold. + -- A2ADispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- + -- -- Now Setup the squadron tanker. + -- A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + function AI_A2A_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.TankerName = TankerName + + return self + end + + + --- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender ) + function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() self.Defenders[ DefenderName ] = Squadron - Squadron.Resources = Squadron.Resources - Defender:GetSize() - self:F( { DefenderName = DefenderName, SquadronResources = Squadron.Resources } ) + if Squadron.Resources then + Squadron.Resources = Squadron.Resources - Size + end + self:E( { DefenderName = DefenderName, SquadronResources = Squadron.Resources } ) end --- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() - Squadron.Resources = Squadron.Resources + Defender:GetSize() + if Squadron.Resources then + Squadron.Resources = Squadron.Resources + Defender:GetSize() + end self.Defenders[ DefenderName ] = nil self:F( { DefenderName = DefenderName, SquadronResources = Squadron.Resources } ) end @@ -1535,12 +2499,14 @@ do -- AI_A2A_DISPATCHER local DefenderSquadron = self.DefenderSquadrons[SquadronName] if DefenderSquadron then for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - if DefenderTask.Type == "CAP" then - if AIGroup:IsAlive() then - -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! - -- The CAP could be damaged, lost control, or out of fuel! - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) then - CapCount = CapCount + 1 + if DefenderTask.SquadronName == SquadronName then + if DefenderTask.Type == "CAP" then + if AIGroup:IsAlive() then + -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! + -- The CAP could be damaged, lost control, or out of fuel! + if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" )then + CapCount = CapCount + 1 + end end end end @@ -1553,37 +2519,50 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:CountDefendersEngaged( Target ) + function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) -- First, count the active AIGroups Units, targetting the DetectedSet - local AIUnitCount = 0 + local DefenderCount = 0 + + self:E( "Counting Defenders Engaged for Attacker:" ) + local DetectedSet = AttackerDetection.Set + DetectedSet:Flush() local DefenderTasks = self:GetDefenderTasks() - for AIGroup, DefenderTask in pairs( DefenderTasks ) do - local AIGroup = AIGroup -- Wrapper.Group#GROUP - local DefenderTask = self:GetDefenderTaskTarget( AIGroup ) - if DefenderTask and DefenderTask.Index == Target.Index then - AIUnitCount = AIUnitCount + AIGroup:GetSize() + for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do + local Defender = DefenderGroup -- Wrapper.Group#GROUP + local DefenderTaskTarget = DefenderTask.Target + local DefenderSquadronName = DefenderTask.SquadronName + + if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then + local Squadron = self:GetSquadron( DefenderSquadronName ) + local SquadronOverhead = Squadron.Overhead or self.DefenderDefault.Overhead + + local DefenderSize = Defender:GetInitialSize() + DefenderCount = DefenderCount + DefenderSize / SquadronOverhead + self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) end end - return AIUnitCount + self:F( { DefenderCount = DefenderCount } ) + + return DefenderCount end --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) + function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( AttackerDetection, DefenderCount ) local Friendlies = nil - local DetectedSet = DetectedItem.Set - local DetectedCount = DetectedSet:Count() + local AttackerSet = AttackerDetection.Set + local AttackerCount = AttackerSet:Count() - local AIFriendlies = self:GetAIFriendliesNearBy( DetectedItem ) + local DefenderFriendlies = self:GetAIFriendliesNearBy( AttackerDetection ) - for FriendlyDistance, AIFriendly in UTILS.spairs( AIFriendlies or {} ) do + for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do -- We only allow to ENGAGE targets as long as the Units on both sides are balanced. - if DetectedCount > DefenderCount then + if AttackerCount > DefenderCount then local Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP if Friendly and Friendly:IsAlive() then -- Ok, so we have a friendly near the potential target. @@ -1592,7 +2571,7 @@ do -- AI_A2A_DISPATCHER if DefenderTask then -- The Task should be CAP or GCI if DefenderTask.Type == "CAP" or DefenderTask.Type == "GCI" then - -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the TargetSet + -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet if DefenderTask.Target == nil then if DefenderTask.Fsm:Is( "Returning" ) or DefenderTask.Fsm:Is( "Patrolling" ) then @@ -1631,22 +2610,65 @@ do -- AI_A2A_DISPATCHER if Cap then - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] - Spawn:InitGrouping( DefenderSquadron.Grouping ) + local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN + local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping + Spawn:InitGrouping( DefenderGrouping ) local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local DefenderCAP = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod ) - self:AddDefenderToSquadron( DefenderSquadron, DefenderCAP ) + local DefenderCAP = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) + self:AddDefenderToSquadron( DefenderSquadron, DefenderCAP, DefenderGrouping ) if DefenderCAP then local Fsm = AI_A2A_CAP:New( DefenderCAP, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.AltType ) Fsm:SetDispatcher( self ) Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + Fsm:SetDisengageRadius( self.DisengageRadius ) + Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) Fsm:Start() - Fsm:__Patrol( 1 ) - self:SetDefenderTask( DefenderCAP, "CAP", Fsm ) + self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) + + function Fsm:onafterTakeoff( Defender, From, Event, To ) + self:F({"GCI Birth", Defender:GetName()}) + --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + + if Squadron then + Fsm:__Patrol( 2 ) -- Start Patrolling + end + end + + function Fsm:onafterRTB( Defender, From, Event, To ) + self:F({"CAP RTB", Defender:GetName()}) + self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + Dispatcher:ClearDefenderTaskTarget( Defender ) + end + + --- @param #AI_A2A_DISPATCHER self + function Fsm:onafterHome( Defender, From, Event, To, Action ) + self:E({"CAP Home", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + + if Action and Action == "Destroy" then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + + if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + end + end end end @@ -1656,38 +2678,16 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, Target, AIGroups ) + function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) - if AIGroups then + if Defenders then - for AIGroupID, AIGroup in pairs( AIGroups ) do + for DefenderID, Defender in pairs( Defenders ) do - local Fsm = self:GetDefenderTaskFsm( AIGroup ) - Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit + local Fsm = self:GetDefenderTaskFsm( Defender ) + Fsm:__Engage( 1, AttackerDetection.Set ) -- Engage on the TargetSetUnit - self:SetDefenderTaskTarget( AIGroup, Target ) - - function Fsm:onafterRTB( AIGroup, From, Event, To ) - self:F({"CAP RTB"}) - self:GetParent(self).onafterRTB( self, AIGroup, From, Event, To ) - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local AIGroup = self:GetControllable() - Dispatcher:ClearDefenderTaskTarget( AIGroup ) - end - - --- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To ) - self:F({"CAP Home"}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local AIGroup = self:GetControllable() - local Squadron = Dispatcher:GetSquadronFromDefender( AIGroup ) - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, AIGroup ) - AIGroup:Destroy() - end - end + self:SetDefenderTaskTarget( Defender, AttackerDetection ) end end @@ -1695,118 +2695,193 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, Target, DefendersMissing, AIGroups ) + function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) - local AttackerCount = Target.Set:Count() - local DefendersCount = 0 + self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) - for AIGroupID, AIGroup in pairs( AIGroups or {} ) do - - local Fsm = self:GetDefenderTaskFsm( AIGroup ) - Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( AIGroup, Target ) - - DefendersCount = DefendersCount + AIGroup:GetSize() - end - - DefendersCount = DefendersMissing - - local ClosestDistance = 0 - local ClosestDefenderSquadronName = nil + local AttackerSet = AttackerDetection.Set + local AttackerUnit = AttackerSet:GetFirst() - while( DefendersCount > 0 ) do - - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do - for InterceptID, Intercept in pairs( DefenderSquadron.Gci or {} ) do - - local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local TargetCoord = Target.Set:GetFirst():GetCoordinate() - local Distance = SpawnCoord:Get2DDistance( TargetCoord ) - - if ClosestDistance == 0 or Distance < ClosestDistance then - - -- Only intercept if the distance to target is smaller or equal to the GciRadius limit. - if Distance <= self.GciRadius then - ClosestDistance = Distance - ClosestDefenderSquadronName = SquadronName - end - end - end + if AttackerUnit and AttackerUnit:IsAlive() then + local AttackerCount = AttackerSet:Count() + local DefenderCount = 0 + + for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do + + local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) + Fsm:__Engage( 1, AttackerSet ) -- Engage on the TargetSetUnit + + self:SetDefenderTaskTarget( DefenderGroup, AttackerDetection ) + + DefenderCount = DefenderCount + DefenderGroup:GetSize() end + + self:F( { DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) + DefenderCount = DefendersMissing + + local ClosestDistance = 0 + local ClosestDefenderSquadronName = nil - if ClosestDefenderSquadronName then + local BreakLoop = false - local DefenderSquadron = self:CanGCI( ClosestDefenderSquadronName ) - - if DefenderSquadron then + while( DefenderCount > 0 and not BreakLoop ) do + + self:F( { DefenderSquadrons = self.DefenderSquadrons } ) - local Gci = self.DefenderSquadrons[ClosestDefenderSquadronName].Gci - - if Gci then - - local DefenderOverhead = DefenderSquadron.Overhead - local DefenderGrouping = DefenderSquadron.Grouping - local DefendersNeeded = math.ceil( DefendersCount * DefenderOverhead ) - - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] - if DefenderGrouping then - Spawn:InitGrouping( ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded ) - else - Spawn:InitGrouping() - end - - local TakeoffMethod = self:GetSquadronTakeoff( ClosestDefenderSquadronName ) - local DefenderGCI = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod ) - self:F( { GCIDefender = DefenderGCI:GetName() } ) - - self:AddDefenderToSquadron( DefenderSquadron, DefenderGCI ) - + for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do + + self:F( { GCI = DefenderSquadron.Gci } ) + + for InterceptID, Intercept in pairs( DefenderSquadron.Gci or {} ) do - if DefenderGCI then - - DefendersCount = DefendersCount - DefenderGCI:GetSize() + self:F( { DefenderSquadron } ) + local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE + local AttackerCoord = AttackerUnit:GetCoordinate() + local InterceptCoord = AttackerDetection.InterceptCoord + self:F( { InterceptCoord = InterceptCoord } ) + if InterceptCoord then + local InterceptDistance = SpawnCoord:Get2DDistance( InterceptCoord ) + local AirbaseDistance = SpawnCoord:Get2DDistance( AttackerCoord ) + self:F( { InterceptDistance = InterceptDistance, AirbaseDistance = AirbaseDistance, InterceptCoord = InterceptCoord } ) - local Fsm = AI_A2A_GCI:New( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:Start() - Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit - - - self:SetDefenderTask( DefenderGCI, "GCI", Fsm, Target ) - - - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"GCI RTB"}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + if ClosestDistance == 0 or InterceptDistance < ClosestDistance then - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local AIGroup = self:GetControllable() - Dispatcher:ClearDefenderTaskTarget( AIGroup ) - end - - --- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To ) - self:F({"GCI Home"}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local AIGroup = self:GetControllable() - local Squadron = Dispatcher:GetSquadronFromDefender( AIGroup ) - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, AIGroup ) - AIGroup:Destroy() + -- Only intercept if the distance to target is smaller or equal to the GciRadius limit. + if AirbaseDistance <= self.GciRadius then + ClosestDistance = InterceptDistance + ClosestDefenderSquadronName = SquadronName end end end end end - else - -- There isn't any closest airbase anymore, break the loop. - break - end - end + + if ClosestDefenderSquadronName then + + local DefenderSquadron = self:CanGCI( ClosestDefenderSquadronName ) + + if DefenderSquadron then + + local Gci = self.DefenderSquadrons[ClosestDefenderSquadronName].Gci + + if Gci then + + local DefenderOverhead = DefenderSquadron.Overhead or self.DefenderDefault.Overhead + local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping + local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) + + self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } ) + self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) + self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) + + -- DefenderSquadron.Resources can have the value nil, which expresses unlimited resources. + -- DefendersNeeded cannot exceed DefenderSquadron.Resources! + if DefenderSquadron.Resources and DefendersNeeded > DefenderSquadron.Resources then + DefendersNeeded = DefenderSquadron.Resources + BreakLoop = true + end + + while ( DefendersNeeded > 0 ) do + + local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN + local DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded + if DefenderGrouping then + Spawn:InitGrouping( DefenderGrouping ) + else + Spawn:InitGrouping() + end + + local TakeoffMethod = self:GetSquadronTakeoff( ClosestDefenderSquadronName ) + local DefenderGCI = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP + self:E( { GCIDefender = DefenderGCI:GetName() } ) + + DefendersNeeded = DefendersNeeded - DefenderGrouping + + self:AddDefenderToSquadron( DefenderSquadron, DefenderGCI, DefenderGrouping ) + + if DefenderGCI then + + DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead + + local Fsm = AI_A2A_GCI:New( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed ) + Fsm:SetDispatcher( self ) + Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + Fsm:SetDisengageRadius( self.DisengageRadius ) + Fsm:Start() + + + self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection ) + + + function Fsm:onafterTakeoff( Defender, From, Event, To ) + self:F({"GCI Birth", Defender:GetName()}) + --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) + + if DefenderTarget then + Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit + end + end + + function Fsm:onafterRTB( Defender, From, Event, To ) + self:F({"GCI RTB", Defender:GetName()}) + self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + Dispatcher:ClearDefenderTaskTarget( Defender ) + end + + --- @param #AI_A2A_DISPATCHER self + function Fsm:onafterLostControl( Defender, From, Event, To ) + self:F({"GCI LostControl", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + if Defender:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + end + + --- @param #AI_A2A_DISPATCHER self + function Fsm:onafterHome( Defender, From, Event, To, Action ) + self:F({"GCI Home", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + + if Action and Action == "Destroy" then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + + if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + end + end -- if DefenderGCI then + end -- while ( DefendersNeeded > 0 ) do + end + else + -- No more resources, try something else. + -- Subject for a later enhancement to try to depart from another squadron and disable this one. + BreakLoop = true + break + end + else + -- There isn't any closest airbase anymore, break the loop. + break + end + end -- if DefenderSquadron then + end -- if AttackerUnit end @@ -1843,20 +2918,20 @@ do -- AI_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @return Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. - function AI_A2A_DISPATCHER:EvaluateGCI( Target ) - self:F( { Target.ItemID } ) + function AI_A2A_DISPATCHER:EvaluateGCI( DetectedItem ) + self:F( { DetectedItem.ItemID } ) - local AttackerSet = Target.Set + local AttackerSet = DetectedItem.Set local AttackerCount = AttackerSet:Count() -- First, count the active AIGroups Units, targetting the DetectedSet - local DefenderCount = self:CountDefendersEngaged( Target ) + local DefenderCount = self:CountDefendersEngaged( DetectedItem ) local DefendersMissing = AttackerCount - DefenderCount self:F( { AttackerCount = AttackerCount, DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - local Friendlies = self:CountDefendersToBeEngaged( Target, DefenderCount ) + local Friendlies = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - if Target.IsDetected == true then + if DetectedItem.IsDetected == true then return DefendersMissing, Friendlies end @@ -1881,17 +2956,21 @@ do -- AI_A2A_DISPATCHER for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do local AIGroup = AIGroup -- Wrapper.Group#GROUP if not AIGroup:IsAlive() then - self:ClearDefenderTask( AIGroup ) + local DefenderTaskFsm = self:GetDefenderTaskFsm( AIGroup ) + self:E( { Defender = AIGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } ) + if not DefenderTaskFsm:Is( "Started" ) then + self:ClearDefenderTask( AIGroup ) + end else if DefenderTask.Target then - local Target = Detection:GetDetectedItem( DefenderTask.Target.Index ) - if not Target then + local AttackerItem = Detection:GetDetectedItem( DefenderTask.Target.Index ) + if not AttackerItem then self:F( { "Removing obsolete Target:", DefenderTask.Target.Index } ) self:ClearDefenderTaskTarget( AIGroup ) - else if DefenderTask.Target.Set then - if DefenderTask.Target.Set:Count() == 0 then + local AttackerCount = DefenderTask.Target.Set:Count() + if AttackerCount == 0 then self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } ) self:ClearDefenderTaskTarget( AIGroup ) end @@ -1940,7 +3019,16 @@ do -- AI_A2A_DISPATCHER for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) %s", Defender:GetName(), DefenderTask.Type, DefenderTask.Fsm:GetState(), Defender:GetSize(), Defender:HasTask() == true and "Executing" or "Idle" ) ) + local Fuel = Defender:GetFuel() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Defender:GetName(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) end end end @@ -1954,12 +3042,21 @@ do -- AI_A2A_DISPATCHER local Defender = Defender -- Wrapper.Group#GROUP if not DefenderTask.Target then local DefenderHasTask = Defender:HasTask() - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) %s", Defender:GetName(), DefenderTask.Type, DefenderTask.Fsm:GetState(), Defender:GetSize(), Defender:HasTask() == true and "Executing" or "Idle" ) ) + local Fuel = Defender:GetFuel() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Defender:GetName(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) end end Report:Add( string.format( "\n - %d Tasks", TaskCount ) ) - self:T( Report:Text( "\n" ) ) + self:E( Report:Text( "\n" ) ) trigger.action.outText( Report:Text( "\n" ), 25 ) end @@ -2071,51 +3168,82 @@ end do - --- AI_A2A_DISPATCHER_GCICAP class. - -- @type AI_A2A_DISPATCHER_GCICAP + --- @type AI_A2A_GCICAP -- @extends #AI_A2A_DISPATCHER - --- # AI\_A2A\_DISPATCHER\_GCICAP class, extends @{AI#AI_A2A_DISPATCHER} + --- # AI\_A2A\_GCICAP class, extends @{AI_A2A_Dispatcher#AI_A2A_DISPATCHER} -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia1.JPG) -- - -- The @{#AI_A2A_DISPATCHER} class is designed to create an automatic air defence system for a coalition. + -- The AI_A2A_GCICAP class is designed to create an automatic air defence system for a coalition setting up GCI and CAP air defenses. + -- The class derives from @{AI#AI_A2A_DISPATCHER} and thus, all the methods that are defined in the @{AI#AI_A2A_DISPATCHER} class, can be used also in AI\_A2A\_GCICAP. -- + -- ==== + -- + -- # Demo Missions + -- + -- ### [AI\_A2A\_GCICAP for Caucasus](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-200%20-%20AI_A2A%20-%20GCICAP%20Demonstration) + -- ### [AI\_A2A\_GCICAP for NTTR](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-210%20-%20NTTR%20AI_A2A_GCICAP%20Demonstration) + -- ### [AI\_A2A\_GCICAP for Normandy](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-220%20-%20NORMANDY%20AI_A2A_GCICAP%20Demonstration) + -- + -- ### [AI\_A2A\_GCICAP for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching) + -- + -- ==== + -- + -- # YouTube Channel + -- + -- ### [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) + -- + -- === -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) -- - -- It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. - -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept detected enemy aircraft or they run short of fuel and must return to base (RTB). When a CAP flight leaves their zone to perform an interception or return to base a new CAP flight will spawn to take their place. + -- AI\_A2A\_GCICAP includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy + -- air movements that are detected by an airborne or ground based radar network. + -- + -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. + -- + -- The AI_A2A_GCICAP provides a lightweight configuration method using the mission editor. Within a very short time, and with very little coding, + -- the mission designer is able to configure a complete A2A defense system for a coalition using the DCS Mission Editor available functions. + -- Using the DCS Mission Editor, you define borders of the coalition which are guarded by GCICAP, + -- configure airbases to belong to the coalition, define squadrons flying certain types of planes or payloads per airbase, and define CAP zones. + -- **Very little lua needs to be applied, a one liner**, which is fully explained below, which can be embedded + -- right in a DO SCRIPT trigger action or in a larger DO SCRIPT FILE trigger action. + -- + -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept + -- detected enemy aircraft or they run short of fuel and must return to base (RTB). + -- + -- When a CAP flight leaves their zone to perform a GCI or return to base a new CAP flight will spawn to take its place. -- If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. - -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. + -- -- In short it is a plug in very flexible and configurable air defence module for DCS World. -- - -- Note that in order to create a two way A2A defense system, two AI\_A2A\_DISPATCHER_GCICAP defense system may need to be created, for each coalition one. - -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. + -- ==== -- - -- ## 1. AI\_A2A\_DISPATCHER\_GCICAP constructor: + -- # The following actions need to be followed when using AI\_A2A\_GCICAP in your mission: -- - -- The @{#AI_A2A_DISPATCHER_GCICAP.New}() method creates a new AI\_A2A\_DISPATCHER\_GCICAP instance. - -- There are two parameters required, a list of prefix group names that collects the groups of the EWR network, and a radius in meters, - -- that will be used to group the detected targets. + -- ## 1) Configure a working AI\_A2A\_GCICAP defense system for ONE coalition. + -- + -- ### 1.1) Define which airbases are for which coalition. -- - -- ### 1.1. Define the **EWR network**: + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_1.JPG) -- - -- As part of the AI\_A2A\_DISPATCHER\_GCICAP constructor, a list of prefixes must be given of the group names defined within the mission editor, - -- that define the EWR network. + -- Color the airbases red or blue. You can do this by selecting the airbase on the map, and select the coalition blue or red. + -- + -- ### 1.2) Place groups of units given a name starting with a **EWR prefix** of your choice to build your EWR network. + -- + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_2.JPG) + -- + -- **All EWR groups starting with the EWR prefix (text) will be included in the detection system.** -- -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia5.JPG) - -- -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. + -- Additionally, ANY other radar capable unit can be part of the EWR network! + -- Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. -- The position of these units is very important as they need to provide enough coverage -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia7.JPG) - -- -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. -- For example if they are a long way forward and can detect enemy planes on the ground and taking off -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. @@ -2126,63 +3254,494 @@ do -- EWR networks are **dynamically maintained**. By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, -- increasing or decreasing the radar coverage of the Early Warning System. -- - -- See the following example to setup an EWR network containing EWR stations and AWACS. + -- ### 1.3) Place Airplane or Helicopter Groups with late activation switched on above the airbases to define Squadrons. -- - -- -- Setup the A2A GCICAP dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER_GCICAP:New( { "DF CCCP AWACS", "DF CCCP EWR" }, 30000 ) + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_3.JPG) -- - -- The above example creates a new AI_A2A_DISPATCHER_GCICAP instance, and stores this in the variable (object) **A2ADispatcher**. - -- The first parameter is are the prefixes of the group names that define the EWR network. - -- The A2A dispatcher will filter all active groups with a group name starting with **DF CCCP AWACS** or **DF CCCP EWR** to be included in the EWR network. + -- These are **templates**, with a given name starting with a **Template prefix** above each airbase that you wanna have a squadron. + -- These **templates** need to be within 1.5km from the airbase center. They don't need to have a slot at the airplane, they can just be positioned above the airbase, + -- without a route, and should only have ONE unit. -- - -- ### 1.2. Define the detected **target grouping radius**: + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_4.JPG) -- - -- As a second parameter of the @{#AI_A2A_DISPATCHER_GCICAP.New}() method, a radius in meters must be given. The radius indicates that detected targets need to be grouped within a radius of 30km. + -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- + -- ### 1.4) Place floating helicopters to create the CAP zones defined by its route points. + -- + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_5.JPG) + -- + -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- + -- The helicopter indicates the start of the CAP zone. + -- The route points define the form of the CAP zone polygon. + -- + -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_6.JPG) + -- + -- **The place of the helicopter is important, as the airbase closest to the helicopter will be the airbase from where the CAP planes will take off for CAP.** + -- + -- ## 2) There are a lot of defaults set, which can be further modified using the methods in @{AI#AI_A2A_DISPATCHER}: + -- + -- ### 2.1) Planes are taking off in the air from the airbases. + -- + -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiiing airplanes, + -- resulting in the airbase to halt operations. + -- + -- You can change the way how planes take off by using the inherited methods from AI\_A2A\_DISPATCHER: + -- + -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. + -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. + -- + -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. + -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: + -- + -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. + -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. + -- * aircraft may collide at the airbase. + -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... + -- + -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. + -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! + -- + -- ### 2.2) Planes return near the airbase or will land if damaged. + -- + -- When damaged airplanes return to the airbase, they will be routed and will dissapear in the air when they are near the airbase. + -- There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land. + -- + -- You can change the way how planes land by using the inherited methods from AI\_A2A\_DISPATCHER: + -- + -- * @{#AI_A2A_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. + -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. + -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. + -- + -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the + -- A2A defense system, as no new CAP or GCI planes can takeoff. + -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. + -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. + -- + -- ### 2.3) CAP operations setup for specific airbases, will be executed with the following parameters: + -- + -- * The altitude will range between 6000 and 10000 meters. + -- * The CAP speed will vary between 500 and 800 km/h. + -- * The engage speed between 800 and 1200 km/h. + -- + -- You can change or add a CAP zone by using the inherited methods from AI\_A2A\_DISPATCHER: + -- + -- The method @{#AI_A2A_DISPATCHER.SetSquadronCap}() defines a CAP execution for a squadron. + -- + -- Setting-up a CAP zone also requires specific parameters: + -- + -- * The minimum and maximum altitude + -- * The minimum speed and maximum patrol speed + -- * The minimum and maximum engage speed + -- * The type of altitude measurement + -- + -- These define how the squadron will perform the CAP while partrolling. Different terrain types requires different types of CAP. + -- + -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. + -- + -- It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system. + -- + -- For example, the following setup will create a CAP for squadron "Sochi": + -- + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- + -- ### 2.4) Each airbase will perform GCI when required, with the following parameters: + -- + -- * The engage speed is between 800 and 1200 km/h. + -- + -- You can change or add a GCI parameters by using the inherited methods from AI\_A2A\_DISPATCHER: + -- + -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. + -- + -- Setting-up a GCI readiness also requires specific parameters: + -- + -- * The minimum speed and maximum patrol speed + -- + -- Essentially this controls how many flights of GCI aircraft can be active at any time. + -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. + -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, + -- too short will mean that the intruders may have alraedy passed the ideal interception point! + -- + -- For example, the following setup will create a GCI for squadron "Sochi": + -- + -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) + -- + -- ### 2.5) Grouping or detected targets. + -- + -- Detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate + -- group being detected. + -- + -- Targets will be grouped within a radius of 30km by default. + -- + -- The radius indicates that detected targets need to be grouped within a radius of 30km. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. -- Fast planes like in the 80s, need a larger radius than WWII planes. -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! + -- ## 3) Additional notes: -- - -- ## 2. AI_A2A_DISPATCHER_DOCUMENTATION is derived from @{#AI_A2A_DISPATCHER}, - -- so all further documentation needs to be consulted in this class - -- for documentation consistency. + -- In order to create a two way A2A defense system, **two AI\_A2A\_GCICAP defense systems must need to be created**, for each coalition one. + -- Each defense system needs its own EWR network setup, airplane templates and CAP configurations. -- - -- @field #AI_A2A_DISPATCHER_GCICAP - AI_A2A_DISPATCHER_GCICAP = { - ClassName = "AI_A2A_DISPATCHER_GCICAP", + -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. + -- + -- ## 4) Coding examples how to use the AI\_A2A\_GCICAP class: + -- + -- ### 4.1) An easy setup: + -- + -- -- Setup the AI_A2A_GCICAP dispatcher for one coalition, and initialize it. + -- GCI_Red = AI_A2A_GCICAP:New( "EWR CCCP", "SQUADRON CCCP", "CAP CCCP", 2 ) + -- -- + -- The following parameters were given to the :New method of AI_A2A_GCICAP, and mean the following: + -- + -- * `"EWR CCCP"`: Groups of the blue coalition are placed that define the EWR network. These groups start with the name `EWR CCCP`. + -- * `"SQUADRON CCCP"`: Late activated Groups objects of the red coalition are placed above the relevant airbases that will contain these templates in the squadron. + -- These late activated Groups start with the name `SQUADRON CCCP`. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level. + -- * `"CAP CCCP"`: CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. + -- These Helicopter Group objects start with the name `CAP CCCP`, and will be the locations wherein CAP will be performed. + -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. + -- + -- + -- ### 4.2) A more advanced setup: + -- + -- -- Setup the AI_A2A_GCICAP dispatcher for the blue coalition. + -- + -- A2A_GCICAP_Blue = AI_A2A_GCICAP:New( { "BLUE EWR" }, { "104th", "105th", "106th" }, { "104th CAP" }, 4 ) + -- + -- The following parameters for the :New method have the following meaning: + -- + -- * `{ "BLUE EWR" }`: An array of the group name prefixes of the groups of the blue coalition are placed that define the EWR network. These groups start with the name `BLUE EWR`. + -- * `{ "104th", "105th", "106th" } `: An array of the group name prefixes of the Late activated Groups objects of the blue coalition are + -- placed above the relevant airbases that will contain these templates in the squadron. + -- These late activated Groups start with the name `104th` or `105th` or `106th`. + -- * `{ "104th CAP" }`: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, + -- where the route points define the route of the polygon of the CAP Zone. + -- These Helicopter Group objects start with the name `104th CAP`, and will be the locations wherein CAP will be performed. + -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. + -- + -- @field #AI_A2A_GCICAP + AI_A2A_GCICAP = { + ClassName = "AI_A2A_GCICAP", Detection = nil, } - --- AI_A2A_DISPATCHER_GCICAP constructor. - -- @param #AI_A2A_DISPATCHER_GCICAP self - -- @param #list<#string> EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. + --- AI_A2A_GCICAP constructor. + -- @param #AI_A2A_GCICAP self + -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. + -- @param #string TemplatePrefixes A list of template prefixes. + -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). + -- @param #number CapLimit A number of how many CAP maximum will be spawned. -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. - -- @return #AI_A2A_DISPATCHER_GCICAP + -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. + -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. + -- @param #number Resources The amount of resources that will be allocated to each squadron. + -- @return #AI_A2A_GCICAP -- @usage -- - -- -- Set a new AI A2A Dispatcher object, based on an EWR network with a 30 km grouping radius - -- -- This for ground and awacs installations. + -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2 ) -- - -- A2ADispatcher = AI_A2A_DISPATCHER_GCICAP:New( { "BlueEWRGroundRadars", "BlueEWRAwacs" }, 30000 ) + -- @usage -- - function AI_A2A_DISPATCHER_GCICAP:New( EWRPrefixes, GroupingRadius ) + -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. + -- -- The EWR network group prefix is DF CCCP. All groups starting with DF CCCP will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. + -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. + -- + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is nil. No CAP is created. + -- -- The CAP Limit is nil. + -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. + -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. + -- + -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 ) + -- + function AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) - local SetGroup = SET_GROUP:New() - SetGroup:FilterPrefixes( EWRPrefixes ) - SetGroup:FilterStart() + local EWRSetGroup = SET_GROUP:New() + EWRSetGroup:FilterPrefixes( EWRPrefixes ) + EWRSetGroup:FilterStart() - local Detection = DETECTION_AREAS:New( SetGroup, GroupingRadius ) + local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) - local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_DISPATCHER_GCICAP + local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_GCICAP + + self:SetEngageRadius( EngageRadius ) + self:SetGciRadius( GciRadius ) + + -- Determine the coalition of the EWRNetwork, this will be the coalition of the GCICAP. + local EWRFirst = EWRSetGroup:GetFirst() -- Wrapper.Group#GROUP + local EWRCoalition = EWRFirst:GetCoalition() + + -- Determine the airbases belonging to the coalition. + local AirbaseNames = {} -- #list<#string> + for AirbaseID, AirbaseData in pairs( _DATABASE.AIRBASES ) do + local Airbase = AirbaseData -- Wrapper.Airbase#AIRBASE + local AirbaseName = Airbase:GetName() + if Airbase:GetCoalition() == EWRCoalition then + table.insert( AirbaseNames, AirbaseName ) + end + end + + self.Templates = SET_GROUP + :New() + :FilterPrefixes( TemplatePrefixes ) + :FilterOnce() + + -- Setup squadrons + + self:E( { Airbases = AirbaseNames } ) + + self:E( "Defining Templates for Airbases ..." ) + for AirbaseID, AirbaseName in pairs( AirbaseNames ) do + local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE + local AirbaseName = Airbase:GetName() + local AirbaseCoord = Airbase:GetCoordinate() + local AirbaseZone = ZONE_RADIUS:New( "Airbase", AirbaseCoord:GetVec2(), 3000 ) + local Templates = nil + self:E( { Airbase = AirbaseName } ) + for TemplateID, Template in pairs( self.Templates:GetSet() ) do + local Template = Template -- Wrapper.Group#GROUP + local TemplateCoord = Template:GetCoordinate() + if AirbaseZone:IsVec2InZone( TemplateCoord:GetVec2() ) then + Templates = Templates or {} + table.insert( Templates, Template:GetName() ) + self:E( { Template = Template:GetName() } ) + end + end + if Templates then + self:SetSquadron( AirbaseName, AirbaseName, Templates, Resources ) + end + end + + -- Setup CAP. + -- Find for each CAP the nearest airbase to the (start or center) of the zone. + -- CAP will be launched from there. + + self.CAPTemplates = SET_GROUP:New() + self.CAPTemplates:FilterPrefixes( CapPrefixes ) + self.CAPTemplates:FilterOnce() + + self:E( "Setting up CAP ..." ) + for CAPID, CAPTemplate in pairs( self.CAPTemplates:GetSet() ) do + local CAPZone = ZONE_POLYGON:New( CAPTemplate:GetName(), CAPTemplate ) + -- Now find the closest airbase from the ZONE (start or center) + local AirbaseDistance = 99999999 + local AirbaseClosest = nil -- Wrapper.Airbase#AIRBASE + self:E( { CAPZoneGroup = CAPID } ) + for AirbaseID, AirbaseName in pairs( AirbaseNames ) do + local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE + local AirbaseName = Airbase:GetName() + local AirbaseCoord = Airbase:GetCoordinate() + local Squadron = self.DefenderSquadrons[AirbaseName] + if Squadron then + local Distance = AirbaseCoord:Get2DDistance( CAPZone:GetCoordinate() ) + self:E( { AirbaseDistance = Distance } ) + if Distance < AirbaseDistance then + AirbaseDistance = Distance + AirbaseClosest = Airbase + end + end + end + if AirbaseClosest then + self:E( { CAPAirbase = AirbaseClosest:GetName() } ) + self:SetSquadronCap( AirbaseClosest:GetName(), CAPZone, 6000, 10000, 500, 800, 800, 1200, "RADIO" ) + self:SetSquadronCapInterval( AirbaseClosest:GetName(), CapLimit, 300, 600, 1 ) + end + end + + -- Setup GCI. + -- GCI is setup for all Squadrons. + self:E( "Setting up GCI ..." ) + for AirbaseID, AirbaseName in pairs( AirbaseNames ) do + local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE + local AirbaseName = Airbase:GetName() + local Squadron = self.DefenderSquadrons[AirbaseName] + self:E( { Airbase = AirbaseName } ) + if Squadron then + self:E( { GCIAirbase = AirbaseName } ) + self:SetSquadronGci( AirbaseName, 800, 1200 ) + end + end self:__Start( 5 ) + self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) + self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) + + self:HandleEvent( EVENTS.Land ) + self:HandleEvent( EVENTS.EngineShutdown ) + return self end + --- AI_A2A_GCICAP constructor with border. + -- @param #AI_A2A_GCICAP self + -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. + -- @param #string TemplatePrefixes A list of template prefixes. + -- @param #string BorderPrefix A Border Zone Prefix. + -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). + -- @param #number CapLimit A number of how many CAP maximum will be spawned. + -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. + -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. + -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. + -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. + -- @param #number Resources The amount of resources that will be allocated to each squadron. + -- @return #AI_A2A_GCICAP + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. + -- -- The CAP Zone prefix is "CAP Zone". + -- -- The CAP Limit is 2. + -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. + -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, + -- -- will be considered a defense task if the target is within 60km from the defender. + -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. + -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) + -- + -- @usage + -- + -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. + -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. + -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. + -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. + -- -- The CAP Zone prefix is nil. No CAP is created. + -- -- The CAP Limit is nil. + -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. + -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. + -- + -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 ) + -- + function AI_A2A_GCICAP:NewWithBorder( EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) + + local self = AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) + + if BorderPrefix then + self:SetBorderZone( ZONE_POLYGON:New( BorderPrefix, GROUP:FindByName( BorderPrefix ) ) ) + end + + return self + + end + end diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 60da2645c..ac868630f 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 0b280265b..dfffe95b5 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index 391edb33c..426bfea04 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index 1f8212f4d..26fba95c9 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -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 ) diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 1d14674c3..58b8e3191 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index 6644c278e..a62f6d28d 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -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 diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index d11687396..cc2dafad9 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -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 ) diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 286332c87..fe3474b0d 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -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, diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua index 8b3785dd2..0cc641623 100644 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ b/Moose Development/Moose/Actions/Act_Account.lua @@ -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 ) diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua index e04139698..df82b8172 100644 --- a/Moose Development/Moose/Actions/Act_Assign.lua +++ b/Moose Development/Moose/Actions/Act_Assign.lua @@ -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 diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua index 4c36460a9..4e0527d37 100644 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -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 diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index e06eaf308..4519a61f3 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Core/Cargo.lua b/Moose Development/Moose/Core/Cargo.lua index 230cc4e05..3d7d4ec14 100644 --- a/Moose Development/Moose/Core/Cargo.lua +++ b/Moose Development/Moose/Core/Cargo.lua @@ -164,7 +164,7 @@ do -- CARGO -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... + -- @field Wrapper.Client#CLIENT CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. -- @field #boolean Moveable This flag defines if the cargo is moveable. -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. @@ -210,8 +210,7 @@ do -- CARGO -- The state transition method needs to start with the name **OnEnter + the name of the state**. -- These state transition methods need to provide a return value, which is specified at the function description. -- - -- @field #CARGO CARGO - -- + -- @field #CARGO CARGO = { ClassName = "CARGO", Type = nil, @@ -251,6 +250,7 @@ function CARGO:New( Type, Name, Weight ) --R2.1 self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) + self:AddTransition( "*", "Damaged", "Damaged" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) self:AddTransition( "*", "Respawn", "UnLoaded" ) @@ -264,17 +264,26 @@ function CARGO:New( Type, Name, Weight ) --R2.1 self.Slingloadable = false self.Moveable = false self.Containable = false + + self:SetDeployed( false ) self.CargoScheduler = SCHEDULER:New() CARGOS[self.Name] = self - self:SetEventPriority( 5 ) - return self end +--- Destroy the cargo. +-- @param #CARGO self +function CARGO:Destroy() + if self.CargoObject then + self.CargoObject:Destroy() + end + self:Destroyed() +end + --- Get the name of the Cargo. -- @param #CARGO self -- @return #string The name of the Cargo. @@ -307,6 +316,13 @@ function CARGO:GetCoordinate() return self.CargoObject:GetCoordinate() end +--- Check if cargo is destroyed. +-- @param #CARGO self +-- @return #boolean true if destroyed +function CARGO:IsDestroyed() + return self:Is( "Destroyed" ) +end + --- Check if cargo is loaded. -- @param #CARGO self @@ -334,6 +350,19 @@ function CARGO:IsAlive() end end +--- Set the cargo as deployed +-- @param #CARGO self +function CARGO:SetDeployed( Deployed ) + self.Deployed = Deployed +end + +--- Is the cargo deployed +-- @param #CARGO self +-- @return #boolean +function CARGO:IsDeployed() + return self.Deployed +end + @@ -345,6 +374,85 @@ function CARGO:Spawn( PointVec2 ) end +--- Signal a flare at the position of the CARGO. +-- @param #CARGO self +-- @param Utilities.Utils#FLARECOLOR FlareColor +function CARGO:Flare( FlareColor ) + if self:IsUnLoaded() then + trigger.action.signalFlare( self.CargoObject:GetVec3(), FlareColor , 0 ) + end +end + +--- Signal a white flare at the position of the CARGO. +-- @param #CARGO self +function CARGO:FlareWhite() + self:Flare( trigger.flareColor.White ) +end + +--- Signal a yellow flare at the position of the CARGO. +-- @param #CARGO self +function CARGO:FlareYellow() + self:Flare( trigger.flareColor.Yellow ) +end + +--- Signal a green flare at the position of the CARGO. +-- @param #CARGO self +function CARGO:FlareGreen() + self:Flare( trigger.flareColor.Green ) +end + +--- Signal a red flare at the position of the CARGO. +-- @param #CARGO self +function CARGO:FlareRed() + self:Flare( trigger.flareColor.Red ) +end + +--- Smoke the CARGO. +-- @param #CARGO self +function CARGO:Smoke( SmokeColor, Range ) + self:F2() + if self:IsUnLoaded() then + if Range then + trigger.action.smoke( self.CargoObject:GetRandomVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self.CargoObject:GetVec3(), SmokeColor ) + end + end +end + +--- Smoke the CARGO Green. +-- @param #CARGO self +function CARGO:SmokeGreen() + self:Smoke( trigger.smokeColor.Green, Range ) +end + +--- Smoke the CARGO Red. +-- @param #CARGO self +function CARGO:SmokeRed() + self:Smoke( trigger.smokeColor.Red, Range ) +end + +--- Smoke the CARGO White. +-- @param #CARGO self +function CARGO:SmokeWhite() + self:Smoke( trigger.smokeColor.White, Range ) +end + +--- Smoke the CARGO Orange. +-- @param #CARGO self +function CARGO:SmokeOrange() + self:Smoke( trigger.smokeColor.Orange, Range ) +end + +--- Smoke the CARGO Blue. +-- @param #CARGO self +function CARGO:SmokeBlue() + self:Smoke( trigger.smokeColor.Blue, Range ) +end + + + + --- Check if Cargo is the given @{Zone}. @@ -357,7 +465,12 @@ function CARGO:IsInZone( Zone ) if self:IsLoaded() then return Zone:IsPointVec2InZone( self.CargoCarrier:GetPointVec2() ) else - return Zone:IsPointVec2InZone( self.CargoObject:GetPointVec2() ) + self:F( { Size = self.CargoObject:GetSize(), Units = self.CargoObject:GetUnits() } ) + if self.CargoObject:GetSize() ~= 0 then + return Zone:IsPointVec2InZone( self.CargoObject:GetPointVec2() ) + else + return false + end end return nil @@ -373,7 +486,8 @@ end function CARGO:IsNear( PointVec2, NearRadius ) self:F( { PointVec2, NearRadius } ) - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + --local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) self:T( Distance ) if Distance <= NearRadius then @@ -415,7 +529,7 @@ do -- CARGO_REPRESENTABLE -- @extends #CARGO -- @field test - --- + --- Models CARGO that is representable by a Unit. -- @field #CARGO_REPRESENTABLE CARGO_REPRESENTABLE CARGO_REPRESENTABLE = { ClassName = "CARGO_REPRESENTABLE" @@ -435,6 +549,18 @@ do -- CARGO_REPRESENTABLE return self end + + --- CARGO_REPRESENTABLE Destructor. + -- @param #CARGO_REPRESENTABLE self + -- @return #CARGO_REPRESENTABLE + function CARGO_REPRESENTABLE:Destroy() + + -- Cargo objects are deleted from the _DATABASE and SET_CARGO objects. + self:F( { CargoName = self:GetName() } ) + _EVENTDISPATCHER:CreateEventDeleteCargo( self ) + + return self + end --- Route a cargo unit to a PointVec2. -- @param #CARGO_REPRESENTABLE self @@ -448,8 +574,8 @@ do -- CARGO_REPRESENTABLE local PointStartVec2 = self.CargoObject:GetPointVec2() - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) + Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) local TaskRoute = self.CargoObject:TaskRoute( Points ) self.CargoObject:SetTask( TaskRoute, 2 ) @@ -480,8 +606,12 @@ end -- CARGO_REPRESENTABLE local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight ) ) -- #CARGO_REPORTABLE self:F( { Type, Name, Weight, ReportRadius } ) + self.CargoSet = SET_CARGO:New() -- Core.Set#SET_CARGO + self.ReportRadius = ReportRadius or 1000 self.CargoObject = CargoObject + + return self end @@ -511,7 +641,7 @@ end -- CARGO_REPRESENTABLE end --- Send a CC message to a GROUP. - -- @param #COMMANDCENTER self + -- @param #CARGO_REPORTABLE 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. @@ -524,17 +654,43 @@ end -- CARGO_REPRESENTABLE end --- Get the range till cargo will board. - -- @param #CARGO self + -- @param #CARGO_REPORTABLE self -- @return #number The range till cargo will board. function CARGO_REPORTABLE:GetBoardingRange() return self.ReportRadius end + + --- Respawn the cargo. + -- @param #CARGO_REPORTABLE self + function CARGO_REPORTABLE:Respawn() + self:F({"Respawning"}) + + for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do + local Cargo = CargoData -- #CARGO + Cargo:Destroy() + Cargo:SetStartState( "UnLoaded" ) + end + + local CargoObject = self.CargoObject -- Wrapper.Group#GROUP + CargoObject:Destroy() + local Template = CargoObject:GetTemplate() + CargoObject:Respawn( Template ) + + self:SetDeployed( false ) + + local WeightGroup = 0 + + self:SetStartState( "UnLoaded" ) + + end + + end do -- CARGO_UNIT - --- Hello + --- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded. -- @type CARGO_UNIT -- @extends #CARGO_REPRESENTABLE @@ -551,343 +707,425 @@ do -- CARGO_UNIT ClassName = "CARGO_UNIT" } ---- CARGO_UNIT Constructor. --- @param #CARGO_UNIT self --- @param Wrapper.Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_UNIT -function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT - self:F( { Type, Name, Weight, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self:T( self.ClassName ) - - self:HandleEvent( EVENTS.Dead, - --- @param #CARGO Cargo - -- @param Core.Event#EVENTDATA EventData - function( Cargo, EventData ) - if Cargo:GetObjectName() == EventData.IniUnit:GetName() then - self:E( { "Cargo destroyed", Cargo } ) - Cargo:Destroyed() - end - end - ) - - return self -end - ---- CARGO_UNIT Destructor. --- @param #CARGO_UNIT self --- @return #CARGO_UNIT -function CARGO_UNIT:Destroy() - - -- Cargo objects are deleted from the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventDeleteCargo( self ) - - return self -end - ---- Enter UnBoarding State. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 60 - local DeployDistance = 9 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - - - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 - ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 - local DirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2) - local Angle = CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3) - - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, Angle ) - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = CargoCarrierPointVec2:RoutePointGround( Speed ) - - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + --- CARGO_UNIT Constructor. + -- @param #CARGO_UNIT self + -- @param Wrapper.Unit#UNIT CargoUnit + -- @param #string Type + -- @param #string Name + -- @param #number Weight + -- @param #number ReportRadius (optional) + -- @param #number NearRadius (optional) + -- @return #CARGO_UNIT + function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius ) + local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT + self:F( { Type, Name, Weight, NearRadius } ) - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) - end - end - -end - ---- Leave UnBoarding State. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2, NearRadius ) then - return true - else - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) - end - return false - end - -end - ---- UnBoard Event. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:__UnLoad( 1, ToPointVec2, NearRadius ) - -end - - - ---- Enter UnLoaded State. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 -function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - ---- Board Event. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier, NearRadius } ) - - local NearRadius = NearRadius or 25 + self:T( CargoUnit ) + self.CargoObject = CargoUnit + + self:T( self.ClassName ) + + self:SetEventPriority( 5 ) + + return self + end + + --- Enter UnBoarding State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 25 + + local Angle = 180 + local Speed = 60 + local DeployDistance = 9 + local RouteDistance = 60 + + if From == "Loaded" then + + local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:Load( CargoCarrier, NearRadius, ... ) - else - local Speed = 90 - local Angle = 180 - local Distance = 5 - - NearRadius = NearRadius or 25 - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + + local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) + + + -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 + ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 + local DirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2) + local Angle = CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3) + + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, Angle ) + + local FromPointVec2 = CargoCarrierPointVec2 + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) + self.CargoCarrier = nil + + local Points = {} + Points[#Points+1] = CargoCarrierPointVec2:WaypointGround( Speed ) + + Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - self:__Boarding( -1, CargoCarrier, NearRadius ) - self.RunCount = 0 + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 1 ) + + + self:__UnBoarding( 1, ToPointVec2, NearRadius ) + end end + end -end - - ---- Boarding Event. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number NearRadius -function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) + --- Leave UnBoarding State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + NearRadius = NearRadius or 25 - if CargoCarrier and CargoCarrier:IsAlive() then - if CargoCarrier:InAir() == false then - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:__Load( 1, CargoCarrier, ... ) + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + if self:IsNear( ToPointVec2, NearRadius ) then + return true else - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 1 - if self.RunCount >= 20 then - self.RunCount = 0 - local Speed = 90 - local Angle = 180 - local Distance = 5 + + self:__UnBoarding( 1, ToPointVec2, NearRadius ) + end + return false + end + + end + + --- UnBoard Event. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 25 + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + end + + self:__UnLoad( 1, ToPointVec2, NearRadius ) + + end + + + + --- Enter UnLoaded State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Core.Point#POINT_VEC2 + function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) + self:F( { ToPointVec2, From, Event, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "Loaded" then + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) + self.CargoCarrier = nil + end + + end + + if self.OnUnLoadedCallBack then + self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) + self.OnUnLoadedCallBack = nil + end + + end + + --- Board Event. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { From, Event, To, CargoCarrier, NearRadius } ) + + local NearRadius = NearRadius or 25 + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only move the group to the carrier when the cargo is not in the air + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then + self:Load( CargoCarrier, NearRadius, ... ) + else + local Speed = 90 + local Angle = 180 + local Distance = 5 + + NearRadius = NearRadius or 25 + + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + self:__Boarding( -1, CargoCarrier, NearRadius ) + self.RunCount = 0 + end + end + + end + + + --- Boarding Event. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #number NearRadius + function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) + + + if CargoCarrier and CargoCarrier:IsAlive() then + if CargoCarrier:InAir() == false then + if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then + self:__Load( 1, CargoCarrier, ... ) + else + self:__Boarding( -1, CargoCarrier, NearRadius, ... ) + self.RunCount = self.RunCount + 1 + if self.RunCount >= 20 then + self.RunCount = 0 + local Speed = 90 + local Angle = 180 + local Distance = 5 + + NearRadius = NearRadius or 25 - NearRadius = NearRadius or 25 - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 0.2 ) + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 0.2 ) + end end + else + self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() ) + self:CancelBoarding( CargoCarrier, NearRadius, ... ) + self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) ) end else - self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() ) - self:CancelBoarding( CargoCarrier, NearRadius, ... ) - self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) ) + self:E("Something is wrong") end - else - self:E("Something is wrong") + end -end - - ---- Enter Boarding State. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) - local Speed = 90 - local Angle = 180 - local Distance = 5 - - local NearRadius = NearRadius or 25 - - if From == "UnLoaded" or From == "Boarding" then + --- Enter Boarding State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) + + local Speed = 90 + local Angle = 180 + local Distance = 5 + + local NearRadius = NearRadius or 25 + if From == "UnLoaded" or From == "Boarding" then + + end + end -end - ---- Loaded State. --- @param #CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) - self:F( { From, Event, To, CargoCarrier } ) - - self.CargoCarrier = CargoCarrier + --- Loaded State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) + self:F( { From, Event, To, CargoCarrier } ) - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self:T("Destroying") - self.CargoObject:Destroy() + self.CargoCarrier = CargoCarrier + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self:T("Destroying") + self.CargoObject:Destroy() + end + end + +end -- CARGO_UNIT + + +do -- CARGO_CRATE + + --- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters using the DCS menus. + -- @type CARGO_CRATE + -- @extends #CARGO_REPRESENTABLE + + --- # CARGO\_CRATE class, extends @{#CARGO_REPRESENTABLE} + -- + -- The CARGO\_CRATE class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. + -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers. + -- + -- === + -- + -- @field #CARGO_CRATE + CARGO_CRATE = { + ClassName = "CARGO_CRATE" + } + + --- CARGO_CRATE Constructor. + -- @param #CARGO_CRATE self + -- @param #string CrateName + -- @param #string Type + -- @param #string Name + -- @param #number Weight + -- @param #number ReportRadius (optional) + -- @param #number NearRadius (optional) + -- @return #CARGO_CRATE + function CARGO_CRATE:New( CargoCrateName, Type, Name, NearRadius ) + local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoCrateName, Type, Name, nil, NearRadius ) ) -- #CARGO_CRATE + self:F( { Type, Name, NearRadius } ) + + self:T( CargoCrateName ) + _DATABASE:AddStatic( CargoCrateName ) + + self.CargoObject = STATIC:FindByName( CargoCrateName ) + + self:T( self.ClassName ) + + self:SetEventPriority( 5 ) + + return self + end + + + + --- Enter UnLoaded State. + -- @param #CARGO_CRATE self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Core.Point#POINT_VEC2 + function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 ) + self:F( { ToPointVec2, From, Event, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 10 + + if From == "Loaded" then + local StartCoordinate = self.CargoCarrier:GetCoordinate() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading ) + + ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( ToPointVec2, 0 ) + self.CargoCarrier = nil + end + + end + + if self.OnUnLoadedCallBack then + self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) + self.OnUnLoadedCallBack = nil + end + + end + + + --- Loaded State. + -- @param #CARGO_CRATE self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier ) + self:F( { From, Event, To, CargoCarrier } ) + + self.CargoCarrier = CargoCarrier + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self:T("Destroying") + self.CargoObject:Destroy() + end end -end end - do -- CARGO_GROUP --- @type CARGO_GROUP @@ -916,9 +1154,9 @@ function CARGO_GROUP:New( CargoGroup, Type, Name, ReportRadius ) local self = BASE:Inherit( self, CARGO_REPORTABLE:New( CargoGroup, Type, Name, 0, ReportRadius ) ) -- #CARGO_GROUP self:F( { Type, Name, ReportRadius } ) - self.CargoSet = SET_CARGO:New() - self.CargoObject = CargoGroup + self:SetDeployed( false ) + self.CargoGroup = CargoGroup local WeightGroup = 0 @@ -937,9 +1175,47 @@ function CARGO_GROUP:New( CargoGroup, Type, Name, ReportRadius ) -- Cargo objects are added to the _DATABASE and SET_CARGO objects. _EVENTDISPATCHER:CreateEventNewCargo( self ) + self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) + self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) + self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) + + self:SetEventPriority( 4 ) + return self end +--- @param #CARGO_GROUP self +-- @param Core.Event#EVENTDATA EventData +function CARGO_GROUP:OnEventCargoDead( EventData ) + + local Destroyed = false + + if self:IsDestroyed() or self:IsUnLoaded() then + Destroyed = true + for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do + local Cargo = CargoData -- #CARGO + if Cargo:IsAlive() then + Destroyed = false + else + Cargo:Destroyed() + end + end + else + local CarrierName = self.CargoCarrier:GetName() + if CarrierName == EventData.IniDCSUnitName then + MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() + Destroyed = true + self.CargoCarrier:ClearCargo() + end + end + + if Destroyed then + self:Destroyed() + self:E( { "Cargo group destroyed" } ) + end + +end + --- Enter Boarding State. -- @param #CARGO_GROUP self -- @param Wrapper.Unit#UNIT CargoCarrier @@ -981,7 +1257,7 @@ function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... ) end end - self.CargoObject:Destroy() + --self.CargoObject:Destroy() self.CargoCarrier = CargoCarrier end @@ -1037,6 +1313,14 @@ function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, end +--- Get the amount of cargo units in the group. +-- @param #CARGO_GROUP self +-- @return #CARGO_GROUP +function CARGO_GROUP:GetCount() + return self.CargoSet:Count() +end + + --- Enter UnBoarding State. -- @param #CARGO_GROUP self -- @param Core.Point#POINT_VEC2 ToPointVec2 @@ -1051,6 +1335,10 @@ function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius local Timer = 1 if From == "Loaded" then + + if self.CargoObject then + self.CargoObject:Destroy() + end -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 self.CargoSet:ForEach( @@ -1134,7 +1422,9 @@ function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 self.CargoSet:ForEach( function( Cargo ) - Cargo:UnLoad( ToPointVec2 ) + --Cargo:UnLoad( ToPointVec2 ) + local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10) + Cargo:UnLoad( RandomVec2 ) end ) @@ -1142,6 +1432,23 @@ function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) end + + --- Respawn the cargo when destroyed + -- @param #CARGO_GROUP self + -- @param #boolean RespawnDestroyed + function CARGO_GROUP:RespawnOnDestroyed( RespawnDestroyed ) + self:F({"In function RespawnOnDestroyed"}) + if RespawnDestroyed then + self.onenterDestroyed = function( self ) + self:F("IN FUNCTION") + self:Respawn() + end + else + self.onenterDestroyed = nil + end + + end + end -- CARGO_GROUP do -- CARGO_PACKAGE @@ -1198,8 +1505,8 @@ function CARGO_PACKAGE:onafterOnBoard( From, Event, To, CargoCarrier, Speed, Boa self:T( { CargoCarrierHeading, CargoDeployHeading } ) local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) local TaskRoute = self.CargoCarrier:TaskRoute( Points ) self.CargoCarrier:SetTask( TaskRoute, 1 ) @@ -1275,8 +1582,8 @@ function CARGO_PACKAGE:onafterUnBoard( From, Event, To, CargoCarrier, Speed, UnL self:T( { CargoCarrierHeading, CargoDeployHeading } ) local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) local TaskRoute = CargoCarrier:TaskRoute( Points ) CargoCarrier:SetTask( TaskRoute, 1 ) @@ -1322,8 +1629,8 @@ function CARGO_PACKAGE:onafterLoad( From, Event, To, CargoCarrier, Speed, LoadDi local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) local TaskRoute = self.CargoCarrier:TaskRoute( Points ) self.CargoCarrier:SetTask( TaskRoute, 1 ) @@ -1348,8 +1655,8 @@ function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Dist self.CargoCarrier = CargoCarrier local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) local TaskRoute = self.CargoCarrier:TaskRoute( Points ) self.CargoCarrier:SetTask( TaskRoute, 1 ) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 788aa2c00..3993898f3 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -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 + + diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 41b982cea..6d90b7b17 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -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 diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 385f50a27..d934f68ad 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -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() diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua new file mode 100644 index 000000000..6b2a98b3c --- /dev/null +++ b/Moose Development/Moose/Core/Goal.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 0d737c552..4faaa5507 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -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 diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index e632f38fc..5d3450616 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -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 diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b3796752d..d520365cd 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -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 diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua new file mode 100644 index 000000000..653e1f696 --- /dev/null +++ b/Moose Development/Moose/Core/Report.lua @@ -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 diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 51358dee4..adba0c601 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -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 diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index a5609afad..4b712d215 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -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 diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0152e5cb8..bd56a7454 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -113,6 +113,38 @@ function SET_BASE:GetSet() return self.Set end +--- Gets a list of the Names of the Objects in the Set. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:GetSetNames() -- R2.3 + self:F2() + + local Names = {} + + for Name, Object in pairs( self.Set ) do + table.insert( Names, Name ) + end + + return Names +end + + +--- Gets a list of the Objects in the Set. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:GetSetObjects() -- R2.3 + self:F2() + + local Objects = {} + + for Name, Object in pairs( self.Set ) do + table.insert( Objects, Object ) + end + + return Objects +end + + --- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName @@ -217,7 +249,6 @@ function SET_BASE:Count() end - --- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). -- @param #SET_BASE self -- @param #SET_BASE BaseSet @@ -406,44 +437,44 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerEnterUnit( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end +--function SET_BASE:_EventOnPlayerEnterUnit( Event ) +-- self:F3( { Event } ) +-- +-- if Event.IniDCSUnit then +-- local ObjectName, Object = self:AddInDatabase( Event ) +-- self:T3( ObjectName, Object ) +-- if self:IsIncludeObject( Object ) then +-- self:Add( ObjectName, Object ) +-- --self:_EventOnPlayerEnterUnit( Event ) +-- end +-- end +--end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerLeaveUnit( Event ) - self:F3( { Event } ) - - local ObjectName = Event.IniDCSUnit - if Event.IniDCSUnit then - if Event.IniDCSGroup then - local GroupUnits = Event.IniDCSGroup:getUnits() - local PlayerCount = 0 - for _, DCSUnit in pairs( GroupUnits ) do - if DCSUnit ~= Event.IniDCSUnit then - if DCSUnit:getPlayerName() ~= nil then - PlayerCount = PlayerCount + 1 - end - end - end - self:E(PlayerCount) - if PlayerCount == 0 then - self:Remove( Event.IniDCSGroupName ) - end - end - end -end +--function SET_BASE:_EventOnPlayerLeaveUnit( Event ) +-- self:F3( { Event } ) +-- +-- local ObjectName = Event.IniDCSUnit +-- if Event.IniDCSUnit then +-- if Event.IniDCSGroup then +-- local GroupUnits = Event.IniDCSGroup:getUnits() +-- local PlayerCount = 0 +-- for _, DCSUnit in pairs( GroupUnits ) do +-- if DCSUnit ~= Event.IniDCSUnit then +-- if DCSUnit:getPlayerName() ~= nil then +-- PlayerCount = PlayerCount + 1 +-- end +-- end +-- end +-- self:E(PlayerCount) +-- if PlayerCount == 0 then +-- self:Remove( Event.IniDCSGroupName ) +-- end +-- end +-- end +--end -- Iterators @@ -884,6 +915,23 @@ function SET_GROUP:FilterStart() return self end +--- Handles the OnDead or OnCrash event for alive groups set. +-- Note: The GROUP object in the SET_GROUP collection will only be removed if the last unit is destroyed of the GROUP. +-- @param #SET_GROUP self +-- @param Core.Event#EVENTDATA Event +function SET_GROUP:_EventOnDeadOrCrash( Event ) + self:F3( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. + self:Remove( ObjectName ) + end + end + end +end + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_GROUP self @@ -1255,759 +1303,1616 @@ function SET_GROUP:IsIncludeObject( MooseGroup ) return MooseGroupInclude end ---- @type SET_UNIT --- @extends Core.Set#SET_BASE ---- # 3) SET_UNIT class, extends @{Set#SET_BASE} --- --- Mission designers can use the SET_UNIT class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Unit types --- * Starting with certain prefix strings. --- --- ## 3.1) SET_UNIT constructor --- --- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: --- --- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. --- --- ## 3.2) Add or Remove UNIT(s) from SET_UNIT --- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. --- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. --- --- ## 3.3) SET_UNIT filter criteria --- --- You can set filter criteria to define the set of units within the SET_UNIT. --- Filter criteria are defined by: --- --- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). --- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). --- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). --- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). --- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: --- --- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. --- --- ## 3.4) SET_UNIT iterators --- --- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. --- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_UNIT: --- --- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- Planned iterators methods in development are (so these are not yet available): --- --- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. --- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- --- ## 3.5 ) SET_UNIT atomic methods --- --- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT: --- --- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Unit}s in the SET, delimited by a comma. --- --- === --- @field #SET_UNIT SET_UNIT -SET_UNIT = { - ClassName = "SET_UNIT", - Units = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - UnitPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} +do -- SET_UNIT - ---- Get the first unit from the set. --- @function [parent=#SET_UNIT] GetFirst --- @param #SET_UNIT self --- @return Wrapper.Unit#UNIT The UNIT object. - ---- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_UNIT self --- @return #SET_UNIT --- @usage --- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. --- DBObject = SET_UNIT:New() -function SET_UNIT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) -- Core.Set#SET_UNIT - - return self -end - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnit A single UNIT. --- @return #SET_UNIT self -function SET_UNIT:AddUnit( AddUnit ) - self:F2( AddUnit:GetName() ) - - self:Add( AddUnit:GetName(), AddUnit ) - - return self -end - - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnitNames A single name or an array of UNIT names. --- @return #SET_UNIT self -function SET_UNIT:AddUnitsByName( AddUnitNames ) - - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } + --- @type SET_UNIT + -- @extends Core.Set#SET_BASE - self:T( AddUnitNamesArray ) - for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do - self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) - end - - return self -end - ---- Remove UNIT(s) from SET_UNIT. --- @param Core.Set#SET_UNIT self --- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. --- @return self -function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } + --- # 3) SET_UNIT class, extends @{Set#SET_BASE} + -- + -- Mission designers can use the SET_UNIT class to build sets of units belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Unit types + -- * Starting with certain prefix strings. + -- + -- ## 3.1) SET_UNIT constructor + -- + -- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: + -- + -- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. + -- + -- ## 3.2) Add or Remove UNIT(s) from SET_UNIT + -- + -- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. + -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. + -- + -- ## 3.3) SET_UNIT filter criteria + -- + -- You can set filter criteria to define the set of units within the SET_UNIT. + -- Filter criteria are defined by: + -- + -- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). + -- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). + -- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). + -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). + -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). + -- + -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: + -- + -- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT. + -- + -- Planned filter criteria within development are (so these are not yet available): + -- + -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. + -- + -- ## 3.4) SET_UNIT iterators + -- + -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. + -- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_UNIT: + -- + -- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. + -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. + -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. + -- + -- Planned iterators methods in development are (so these are not yet available): + -- + -- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. + -- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. + -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. + -- + -- ## 3.5 ) SET_UNIT atomic methods + -- + -- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT: + -- + -- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Unit}s in the SET, delimited by a comma. + -- + -- === + -- @field #SET_UNIT SET_UNIT + SET_UNIT = { + ClassName = "SET_UNIT", + Units = {}, + Filter = { + Coalitions = nil, + Categories = nil, + Types = nil, + Countries = nil, + UnitPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Unit.Category.AIRPLANE, + helicopter = Unit.Category.HELICOPTER, + ground = Unit.Category.GROUND_UNIT, + ship = Unit.Category.SHIP, + structure = Unit.Category.STRUCTURE, + }, + }, + } - for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do - self:Remove( RemoveUnitName ) + + --- Get the first unit from the set. + -- @function [parent=#SET_UNIT] GetFirst + -- @param #SET_UNIT self + -- @return Wrapper.Unit#UNIT The UNIT object. + + --- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_UNIT self + -- @return #SET_UNIT + -- @usage + -- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. + -- DBObject = SET_UNIT:New() + function SET_UNIT:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) -- Core.Set#SET_UNIT + + return self end + + --- Add UNIT(s) to SET_UNIT. + -- @param #SET_UNIT self + -- @param #string AddUnit A single UNIT. + -- @return #SET_UNIT self + function SET_UNIT:AddUnit( AddUnit ) + self:F2( AddUnit:GetName() ) + + self:Add( AddUnit:GetName(), AddUnit ) + + return self + end + + + --- Add UNIT(s) to SET_UNIT. + -- @param #SET_UNIT self + -- @param #string AddUnitNames A single name or an array of UNIT names. + -- @return #SET_UNIT self + function SET_UNIT:AddUnitsByName( AddUnitNames ) + + local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } - return self -end + self:T( AddUnitNamesArray ) + for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do + self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) + end + + return self + end + + --- Remove UNIT(s) from SET_UNIT. + -- @param Core.Set#SET_UNIT self + -- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. + -- @return self + function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) + + local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } + + for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do + self:Remove( RemoveUnitName ) + end + + return self + end + + + --- Finds a Unit based on the Unit Name. + -- @param #SET_UNIT self + -- @param #string UnitName + -- @return Wrapper.Unit#UNIT The found Unit. + function SET_UNIT:FindUnit( UnitName ) + + local UnitFound = self.Set[UnitName] + return UnitFound + end + + + + --- Builds a set of units of coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_UNIT self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". + -- @return #SET_UNIT self + function SET_UNIT:FilterCoalitions( Coalitions ) - ---- Finds a Unit based on the Unit Name. --- @param #SET_UNIT self --- @param #string UnitName --- @return Wrapper.Unit#UNIT The found Unit. -function SET_UNIT:FindUnit( UnitName ) - - local UnitFound = self.Set[UnitName] - return UnitFound -end - - - ---- Builds a set of units of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_UNIT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_UNIT self -function SET_UNIT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of units out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_UNIT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_UNIT self -function SET_UNIT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of units of defined unit types. --- Possible current types are those types known within DCS world. --- @param #SET_UNIT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of units of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_UNIT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of units of defined unit prefixes. --- All the units starting with the given prefixes will be included within the set. --- @param #SET_UNIT self --- @param #string Prefixes The prefix of which the unit name starts with. --- @return #SET_UNIT self -function SET_UNIT:FilterPrefixes( Prefixes ) - if not self.Filter.UnitPrefixes then - self.Filter.UnitPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.UnitPrefixes[Prefix] = Prefix - end - return self -end - ---- Builds a set of units having a radar of give types. --- All the units having a radar of a given type will be included within the set. --- @param #SET_UNIT self --- @param #table RadarTypes The radar types. --- @return #SET_UNIT self -function SET_UNIT:FilterHasRadar( RadarTypes ) - - self.Filter.RadarTypes = self.Filter.RadarTypes or {} - if type( RadarTypes ) ~= "table" then - RadarTypes = { RadarTypes } - end - for RadarTypeID, RadarType in pairs( RadarTypes ) do - self.Filter.RadarTypes[RadarType] = RadarType - end - return self -end - ---- Builds a set of SEADable units. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterHasSEAD() - - self.Filter.SEAD = true - return self -end - - - ---- Starts the filtering. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:AddInDatabase( Event ) - self:F3( { Event } ) - - if Event.IniObjectCategory == 1 then - if not self.Database[Event.IniDCSUnitName] then - self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) - self:T3( self.Database[Event.IniDCSUnitName] ) + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self end - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:FindInDatabase( Event ) - self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] -end - ---- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #SET_UNIT self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnit( IteratorFunction, ... ) - self:F2( arg ) - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- --- @param #SET_UNIT self --- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). --- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self --- @usage --- --- UnitSet:ForEachUnitPerThreatLevel( 10, 0, --- -- @param Wrapper.Unit#UNIT UnitObject The UNIT object in the UnitSet, that will be passed to the local function for evaluation. --- function( UnitObject ) --- .. logic .. --- end --- ) --- -function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation - self:F2( arg ) + --- Builds a set of units out of categories. + -- Possible current categories are plane, helicopter, ground, ship. + -- @param #SET_UNIT self + -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". + -- @return #SET_UNIT self + function SET_UNIT:FilterCategories( Categories ) + if not self.Filter.Categories then + self.Filter.Categories = {} + end + if type( Categories ) ~= "table" then + Categories = { Categories } + end + for CategoryID, Category in pairs( Categories ) do + self.Filter.Categories[Category] = Category + end + return self + end - local ThreatLevelSet = {} - if self:Count() ~= 0 then - for UnitName, UnitObject in pairs( self.Set ) do - local Unit = UnitObject -- Wrapper.Unit#UNIT - - local ThreatLevel = Unit:GetThreatLevel() - ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {} - ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {} - ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject - self:E( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) + --- Builds a set of units of defined unit types. + -- Possible current types are those types known within DCS world. + -- @param #SET_UNIT self + -- @param #string Types Can take those type strings known within DCS world. + -- @return #SET_UNIT self + function SET_UNIT:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self + end + + + --- Builds a set of units of defined countries. + -- Possible current countries are those known within DCS world. + -- @param #SET_UNIT self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_UNIT self + function SET_UNIT:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self + end + + + --- Builds a set of units of defined unit prefixes. + -- All the units starting with the given prefixes will be included within the set. + -- @param #SET_UNIT self + -- @param #string Prefixes The prefix of which the unit name starts with. + -- @return #SET_UNIT self + function SET_UNIT:FilterPrefixes( Prefixes ) + if not self.Filter.UnitPrefixes then + self.Filter.UnitPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.UnitPrefixes[Prefix] = Prefix + end + return self + end + + --- Builds a set of units having a radar of give types. + -- All the units having a radar of a given type will be included within the set. + -- @param #SET_UNIT self + -- @param #table RadarTypes The radar types. + -- @return #SET_UNIT self + function SET_UNIT:FilterHasRadar( RadarTypes ) + + self.Filter.RadarTypes = self.Filter.RadarTypes or {} + if type( RadarTypes ) ~= "table" then + RadarTypes = { RadarTypes } + end + for RadarTypeID, RadarType in pairs( RadarTypes ) do + self.Filter.RadarTypes[RadarType] = RadarType + end + return self + end + + --- Builds a set of SEADable units. + -- @param #SET_UNIT self + -- @return #SET_UNIT self + function SET_UNIT:FilterHasSEAD() + + self.Filter.SEAD = true + return self + end + + + + --- Starts the filtering. + -- @param #SET_UNIT self + -- @return #SET_UNIT self + function SET_UNIT:FilterStart() + + if _DATABASE then + self:_FilterStart() end - local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 + return self + end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. + -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! + -- @param #SET_UNIT self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the UNIT + -- @return #table The UNIT + function SET_UNIT:AddInDatabase( Event ) + self:F3( { Event } ) + + if Event.IniObjectCategory == 1 then + if not self.Database[Event.IniDCSUnitName] then + self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) + self:T3( self.Database[Event.IniDCSUnitName] ) + end + end - for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do - self:E( { ThreatLevel = ThreatLevel } ) - local ThreatLevelItem = ThreatLevelSet[ThreatLevel] - if ThreatLevelItem then - self:ForEach( IteratorFunction, arg, ThreatLevelItem.Set ) - end - end + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - return self -end - - - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! + -- @param #SET_UNIT self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the UNIT + -- @return #table The UNIT + function SET_UNIT:FindInDatabase( Event ) + self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Returns map of unit types. --- @param #SET_UNIT self --- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. -function SET_UNIT:GetUnitTypes() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Wrapper.Unit#UNIT - if TextUnit:IsAlive() then - local UnitType = TextUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return UnitTypes -end - - ---- Returns a comma separated string of the unit types with a count in the @{Set}. --- @param #SET_UNIT self --- @return #string The unit types string -function SET_UNIT:GetUnitTypesText() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = self:GetUnitTypes() - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) -end - ---- Returns map of unit threat levels. --- @param #SET_UNIT self --- @return #table. -function SET_UNIT:GetUnitThreatLevels() - self:F2() - - local UnitThreatLevels = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - if ThreatUnit:IsAlive() then - local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() - local ThreatUnitName = ThreatUnit:GetName() - - UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} - UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText - UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} - UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit - end - end - - return UnitThreatLevels -end - ---- Calculate the maxium A2G threat level of the SET_UNIT. --- @param #SET_UNIT self -function SET_UNIT:CalculateThreatLevelA2G() - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - return MaxThreatLevelA2G - -end - - ---- Returns if the @{Set} has targets having a radar (of a given type). --- @param #SET_UNIT self --- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType --- @return #number The amount of radars in the Set with the given type -function SET_UNIT:HasRadar( RadarType ) - self:F2( RadarType ) - - local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT - local HasSensors - if RadarType then - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) - else - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) - end - self:T3(HasSensors) - if HasSensors then - RadarCount = RadarCount + 1 - end - end - - return RadarCount -end - ---- Returns if the @{Set} has targets that can be SEADed. --- @param #SET_UNIT self --- @return #number The amount of SEADable units in the Set -function SET_UNIT:HasSEAD() - self:F2() - - local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Wrapper.Unit#UNIT - if UnitSEAD:IsAlive() then - local UnitSEADAttributes = UnitSEAD:GetDesc().attributes - - local HasSEAD = UnitSEAD:HasSEAD() - - self:T3(HasSEAD) - if HasSEAD then - SEADCount = SEADCount + 1 - end - end - end - - return SEADCount -end - ---- Returns if the @{Set} has ground targets. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasGroundUnits() - self:F2() - - local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsGround() then - GroundUnitCount = GroundUnitCount + 1 - end - end - - return GroundUnitCount -end - ---- Returns if the @{Set} has friendly ground units. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) - self:F2() - - local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsFriendly( FriendlyCoalition ) then - FriendlyUnitCount = FriendlyUnitCount + 1 - end - end - - return FriendlyUnitCount -end - - - ------ Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_UNIT self --- @param Wrapper.Unit#UNIT MUnit --- @return #SET_UNIT self -function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( MUnit ) - local MUnitInclude = true - - if self.Filter.Coalitions then - local MUnitCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then - MUnitCoalition = true - end - end - MUnitInclude = MUnitInclude and MUnitCoalition + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - if self.Filter.Categories then - local MUnitCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then - MUnitCategory = true - end - end - MUnitInclude = MUnitInclude and MUnitCategory - end - if self.Filter.Types then - local MUnitType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) - if TypeName == MUnit:GetTypeName() then - MUnitType = true - end - end - MUnitInclude = MUnitInclude and MUnitType - end + do -- Is Zone methods - if self.Filter.Countries then - local MUnitCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) - if country.id[CountryName] == MUnit:GetCountry() then - MUnitCountry = true - end - end - MUnitInclude = MUnitInclude and MUnitCountry - end - - if self.Filter.UnitPrefixes then - local MUnitPrefix = false - for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) - if string.find( MUnit:GetName(), UnitPrefix, 1 ) then - MUnitPrefix = true - end - end - MUnitInclude = MUnitInclude and MUnitPrefix - end - - if self.Filter.RadarTypes then - local MUnitRadar = false - for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) - if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then - if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) + --- Check if minimal one element of the SET_UNIT is in the Zone. + -- @param #SET_UNIT self + -- @param Core.Zone#ZONE ZoneTest The Zone to be tested for. + -- @return #boolean + function SET_UNIT:IsPartiallyInZone( ZoneTest ) + + local IsPartiallyInZone = false + + local function EvaluateZone( ZoneUnit ) + + local ZoneUnitName = ZoneUnit:GetName() + self:E( { ZoneUnitName = ZoneUnitName } ) + if self:FindUnit( ZoneUnitName ) then + IsPartiallyInZone = true + self:E( { Found = true } ) + return false + end + + return true + end + + ZoneTest:SearchZone( EvaluateZone ) + + return IsPartiallyInZone + end + + + --- Check if no element of the SET_UNIT is in the Zone. + -- @param #SET_UNIT self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @return #boolean + function SET_UNIT:IsNotInZone( Zone ) + + local IsNotInZone = true + + local function EvaluateZone( ZoneUnit ) + + local ZoneUnitName = ZoneUnit:GetName() + if self:FindUnit( ZoneUnitName ) then + IsNotInZone = false + return false + end + + return true + end + + Zone:SearchZone( EvaluateZone ) + + return IsNotInZone + end + + + --- Check if minimal one element of the SET_UNIT is in the Zone. + -- @param #SET_UNIT self + -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. + -- @return #SET_UNIT self + function SET_UNIT:ForEachUnitInZone( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self + end + + + end + + + --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + -- @param #SET_UNIT self + -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. + -- @return #SET_UNIT self + function SET_UNIT:ForEachUnit( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self + end + + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + -- + -- @param #SET_UNIT self + -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). + -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). + -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. + -- @return #SET_UNIT self + -- @usage + -- + -- UnitSet:ForEachUnitPerThreatLevel( 10, 0, + -- -- @param Wrapper.Unit#UNIT UnitObject The UNIT object in the UnitSet, that will be passed to the local function for evaluation. + -- function( UnitObject ) + -- .. logic .. + -- end + -- ) + -- + function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation + self:F2( arg ) + + local ThreatLevelSet = {} + + if self:Count() ~= 0 then + for UnitName, UnitObject in pairs( self.Set ) do + local Unit = UnitObject -- Wrapper.Unit#UNIT + + local ThreatLevel = Unit:GetThreatLevel() + ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {} + ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {} + ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject + self:E( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) + end + + local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 + + for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do + self:E( { ThreatLevel = ThreatLevel } ) + local ThreatLevelItem = ThreatLevelSet[ThreatLevel] + if ThreatLevelItem then + self:ForEach( IteratorFunction, arg, ThreatLevelItem.Set ) end - MUnitRadar = true end end - MUnitInclude = MUnitInclude and MUnitRadar + + return self end - - if self.Filter.SEAD then - local MUnitSEAD = false - if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) - MUnitSEAD = true + + + + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. + -- @param #SET_UNIT self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. + -- @return #SET_UNIT self + function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self + end + + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. + -- @param #SET_UNIT self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. + -- @return #SET_UNIT self + function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self + end + + --- Returns map of unit types. + -- @param #SET_UNIT self + -- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. + function SET_UNIT:GetUnitTypes() + self:F2() + + local MT = {} -- Message Text + local UnitTypes = {} + + for UnitID, UnitData in pairs( self:GetSet() ) do + local TextUnit = UnitData -- Wrapper.Unit#UNIT + if TextUnit:IsAlive() then + local UnitType = TextUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end end - MUnitInclude = MUnitInclude and MUnitSEAD + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return UnitTypes end - - self:T2( MUnitInclude ) - return MUnitInclude + + + --- Returns a comma separated string of the unit types with a count in the @{Set}. + -- @param #SET_UNIT self + -- @return #string The unit types string + function SET_UNIT:GetUnitTypesText() + self:F2() + + local MT = {} -- Message Text + local UnitTypes = self:GetUnitTypes() + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return table.concat( MT, ", " ) + end + + --- Returns map of unit threat levels. + -- @param #SET_UNIT self + -- @return #table. + function SET_UNIT:GetUnitThreatLevels() + self:F2() + + local UnitThreatLevels = {} + + for UnitID, UnitData in pairs( self:GetSet() ) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + if ThreatUnit:IsAlive() then + local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() + local ThreatUnitName = ThreatUnit:GetName() + + UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} + UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText + UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} + UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit + end + end + + return UnitThreatLevels + end + + --- Calculate the maxium A2G threat level of the SET_UNIT. + -- @param #SET_UNIT self + -- @return #number The maximum threatlevel + function SET_UNIT:CalculateThreatLevelA2G() + + local MaxThreatLevelA2G = 0 + local MaxThreatText = "" + for UnitName, UnitData in pairs( self:GetSet() ) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + local ThreatLevelA2G, ThreatText = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + MaxThreatText = ThreatText + end + end + + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + return MaxThreatLevelA2G, MaxThreatText + + end + + --- Get the center coordinate of the SET_UNIT. + -- @param #SET_UNIT self + -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units. + function SET_UNIT:GetCoordinate() + + local Coordinate = self:GetFirst():GetCoordinate() + + local x1 = Coordinate.x + local x2 = Coordinate.x + local y1 = Coordinate.y + local y2 = Coordinate.y + local z1 = Coordinate.z + local z2 = Coordinate.z + local MaxVelocity = 0 + local AvgHeading = nil + local MovingCount = 0 + + for UnitName, UnitData in pairs( self:GetSet() ) do + + local Unit = UnitData -- Wrapper.Unit#UNIT + local Coordinate = Unit:GetCoordinate() + + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 + x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 + y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 + y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 + z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 + z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + + local Velocity = Coordinate:GetVelocity() + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + local Heading = Coordinate:GetHeading() + AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + MovingCount = MovingCount + 1 + end + end + + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + + Coordinate.x = ( x2 - x1 ) / 2 + x1 + Coordinate.y = ( y2 - y1 ) / 2 + y1 + Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate:SetHeading( AvgHeading ) + Coordinate:SetVelocity( MaxVelocity ) + + self:F( { Coordinate = Coordinate } ) + return Coordinate + + end + + --- Get the maximum velocity of the SET_UNIT. + -- @param #SET_UNIT self + -- @return #number The speed in mps in case of moving units. + function SET_UNIT:GetVelocity() + + local Coordinate = self:GetFirst():GetCoordinate() + + local MaxVelocity = 0 + + for UnitName, UnitData in pairs( self:GetSet() ) do + + local Unit = UnitData -- Wrapper.Unit#UNIT + local Coordinate = Unit:GetCoordinate() + + local Velocity = Coordinate:GetVelocity() + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + end + end + + self:F( { MaxVelocity = MaxVelocity } ) + return MaxVelocity + + end + + --- Get the average heading of the SET_UNIT. + -- @param #SET_UNIT self + -- @return #number Heading Heading in degrees and speed in mps in case of moving units. + function SET_UNIT:GetHeading() + + local HeadingSet = nil + local MovingCount = 0 + + for UnitName, UnitData in pairs( self:GetSet() ) do + + local Unit = UnitData -- Wrapper.Unit#UNIT + local Coordinate = Unit:GetCoordinate() + + local Velocity = Coordinate:GetVelocity() + if Velocity ~= 0 then + local Heading = Coordinate:GetHeading() + if HeadingSet == nil then + HeadingSet = Heading + else + local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + HeadingDiff = math.abs( HeadingDiff ) + if HeadingDiff > 5 then + HeadingSet = nil + break + end + end + end + end + + return HeadingSet + + end + + + + --- Returns if the @{Set} has targets having a radar (of a given type). + -- @param #SET_UNIT self + -- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType + -- @return #number The amount of radars in the Set with the given type + function SET_UNIT:HasRadar( RadarType ) + self:F2( RadarType ) + + local RadarCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT + local HasSensors + if RadarType then + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) + else + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) + end + self:T3(HasSensors) + if HasSensors then + RadarCount = RadarCount + 1 + end + end + + return RadarCount + end + + --- Returns if the @{Set} has targets that can be SEADed. + -- @param #SET_UNIT self + -- @return #number The amount of SEADable units in the Set + function SET_UNIT:HasSEAD() + self:F2() + + local SEADCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSEAD = UnitData -- Wrapper.Unit#UNIT + if UnitSEAD:IsAlive() then + local UnitSEADAttributes = UnitSEAD:GetDesc().attributes + + local HasSEAD = UnitSEAD:HasSEAD() + + self:T3(HasSEAD) + if HasSEAD then + SEADCount = SEADCount + 1 + end + end + end + + return SEADCount + end + + --- Returns if the @{Set} has ground targets. + -- @param #SET_UNIT self + -- @return #number The amount of ground targets in the Set. + function SET_UNIT:HasGroundUnits() + self:F2() + + local GroundUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Wrapper.Unit#UNIT + if UnitTest:IsGround() then + GroundUnitCount = GroundUnitCount + 1 + end + end + + return GroundUnitCount + end + + --- Returns if the @{Set} has friendly ground units. + -- @param #SET_UNIT self + -- @return #number The amount of ground targets in the Set. + function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) + self:F2() + + local FriendlyUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Wrapper.Unit#UNIT + if UnitTest:IsFriendly( FriendlyCoalition ) then + FriendlyUnitCount = FriendlyUnitCount + 1 + end + end + + return FriendlyUnitCount + end + + + + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. + ---- @param #SET_UNIT self + ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. + ---- @return #SET_UNIT self + --function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) + -- self:F2( arg ) + -- + -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) + -- + -- return self + --end + -- + -- + ----- Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. + ---- @param #SET_UNIT self + ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. + ---- @return #SET_UNIT self + --function SET_UNIT:ForEachClient( IteratorFunction, ... ) + -- self:F2( arg ) + -- + -- self:ForEach( IteratorFunction, arg, self.Clients ) + -- + -- return self + --end + + + --- + -- @param #SET_UNIT self + -- @param Wrapper.Unit#UNIT MUnit + -- @return #SET_UNIT self + function SET_UNIT:IsIncludeObject( MUnit ) + self:F2( MUnit ) + local MUnitInclude = true + + if self.Filter.Coalitions then + local MUnitCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + self:E( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then + MUnitCoalition = true + end + end + MUnitInclude = MUnitInclude and MUnitCoalition + end + + if self.Filter.Categories then + local MUnitCategory = false + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then + MUnitCategory = true + end + end + MUnitInclude = MUnitInclude and MUnitCategory + end + + if self.Filter.Types then + local MUnitType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) + if TypeName == MUnit:GetTypeName() then + MUnitType = true + end + end + MUnitInclude = MUnitInclude and MUnitType + end + + if self.Filter.Countries then + local MUnitCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) + if country.id[CountryName] == MUnit:GetCountry() then + MUnitCountry = true + end + end + MUnitInclude = MUnitInclude and MUnitCountry + end + + if self.Filter.UnitPrefixes then + local MUnitPrefix = false + for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do + self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) + if string.find( MUnit:GetName(), UnitPrefix, 1 ) then + MUnitPrefix = true + end + end + MUnitInclude = MUnitInclude and MUnitPrefix + end + + if self.Filter.RadarTypes then + local MUnitRadar = false + for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do + self:T3( { "Radar:", RadarType } ) + if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then + if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. + self:T3( "RADAR Found" ) + end + MUnitRadar = true + end + end + MUnitInclude = MUnitInclude and MUnitRadar + end + + if self.Filter.SEAD then + local MUnitSEAD = false + if MUnit:HasSEAD() == true then + self:T3( "SEAD Found" ) + MUnitSEAD = true + end + MUnitInclude = MUnitInclude and MUnitSEAD + end + + self:T2( MUnitInclude ) + return MUnitInclude + end + + + --- Retrieve the type names of the @{Unit}s in the SET, delimited by an optional delimiter. + -- @param #SET_UNIT self + -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @return #string The types of the @{Unit}s delimited. + function SET_UNIT:GetTypeNames( Delimiter ) + + Delimiter = Delimiter or ", " + local TypeReport = REPORT:New() + local Types = {} + + for UnitName, UnitData in pairs( self:GetSet() ) do + + local Unit = UnitData -- Wrapper.Unit#UNIT + local UnitTypeName = Unit:GetTypeName() + + if not Types[UnitTypeName] then + Types[UnitTypeName] = UnitTypeName + TypeReport:Add( UnitTypeName ) + end + end + + return TypeReport:Text( Delimiter ) + end + end +do -- SET_STATIC ---- Retrieve the type names of the @{Unit}s in the SET, delimited by an optional delimiter. --- @param #SET_UNIT self --- @param #string Delimiter (optional) The delimiter, which is default a comma. --- @return #string The types of the @{Unit}s delimited. -function SET_UNIT:GetTypeNames( Delimiter ) - - Delimiter = Delimiter or ", " - local TypeReport = REPORT:New() - local Types = {} + --- @type SET_STATIC + -- @extends Core.Set#SET_BASE - for UnitName, UnitData in pairs( self:GetSet() ) do + --- # 3) SET_STATIC class, extends @{Set#SET_BASE} + -- + -- Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Static types + -- * Starting with certain prefix strings. + -- + -- ## 3.1) SET_STATIC constructor + -- + -- Create a new SET_STATIC object with the @{#SET_STATIC.New} method: + -- + -- * @{#SET_STATIC.New}: Creates a new SET_STATIC object. + -- + -- ## 3.2) Add or Remove STATIC(s) from SET_STATIC + -- + -- STATICs can be added and removed using the @{Set#SET_STATIC.AddStaticsByName} and @{Set#SET_STATIC.RemoveStaticsByName} respectively. + -- These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC. + -- + -- ## 3.3) SET_STATIC filter criteria + -- + -- You can set filter criteria to define the set of units within the SET_STATIC. + -- Filter criteria are defined by: + -- + -- * @{#SET_STATIC.FilterCoalitions}: Builds the SET_STATIC with the units belonging to the coalition(s). + -- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies). + -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s). + -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). + -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s). + -- + -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: + -- + -- * @{#SET_STATIC.FilterStart}: Starts the filtering of the units within the SET_STATIC. + -- + -- Planned filter criteria within development are (so these are not yet available): + -- + -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Zone#ZONE}. + -- + -- ## 3.4) SET_STATIC iterators + -- + -- Once the filters have been defined and the SET_STATIC has been built, you can iterate the SET_STATIC with the available iterator methods. + -- The iterator methods will walk the SET_STATIC set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_STATIC: + -- + -- * @{#SET_STATIC.ForEachStatic}: Calls a function for each alive unit it finds within the SET_STATIC. + -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. + -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. + -- + -- Planned iterators methods in development are (so these are not yet available): + -- + -- * @{#SET_STATIC.ForEachStaticInZone}: Calls a function for each unit contained within the SET_STATIC. + -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- + -- ## 3.5 ) SET_STATIC atomic methods + -- + -- Various methods exist for a SET_STATIC to perform actions or calculations and retrieve results from the SET_STATIC: + -- + -- * @{#SET_STATIC.GetTypeNames}(): Retrieve the type names of the @{Static}s in the SET, delimited by a comma. + -- + -- === + -- @field #SET_STATIC SET_STATIC + SET_STATIC = { + ClassName = "SET_STATIC", + Statics = {}, + Filter = { + Coalitions = nil, + Categories = nil, + Types = nil, + Countries = nil, + StaticPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Unit.Category.AIRPLANE, + helicopter = Unit.Category.HELICOPTER, + ground = Unit.Category.GROUND_STATIC, + ship = Unit.Category.SHIP, + structure = Unit.Category.STRUCTURE, + }, + }, + } - local Unit = UnitData -- Wrapper.Unit#UNIT - local UnitTypeName = Unit:GetTypeName() - - if not Types[UnitTypeName] then - Types[UnitTypeName] = UnitTypeName - TypeReport:Add( UnitTypeName ) - end + + --- Get the first unit from the set. + -- @function [parent=#SET_STATIC] GetFirst + -- @param #SET_STATIC self + -- @return Wrapper.Static#STATIC The STATIC object. + + --- Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_STATIC self + -- @return #SET_STATIC + -- @usage + -- -- Define a new SET_STATIC Object. This DBObject will contain a reference to all alive Statics. + -- DBObject = SET_STATIC:New() + function SET_STATIC:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.STATICS ) ) -- Core.Set#SET_STATIC + + return self + end + + --- Add STATIC(s) to SET_STATIC. + -- @param #SET_STATIC self + -- @param #string AddStatic A single STATIC. + -- @return #SET_STATIC self + function SET_STATIC:AddStatic( AddStatic ) + self:F2( AddStatic:GetName() ) + + self:Add( AddStatic:GetName(), AddStatic ) + + return self + end + + + --- Add STATIC(s) to SET_STATIC. + -- @param #SET_STATIC self + -- @param #string AddStaticNames A single name or an array of STATIC names. + -- @return #SET_STATIC self + function SET_STATIC:AddStaticsByName( AddStaticNames ) + + local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames } + + self:T( AddStaticNamesArray ) + for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do + self:Add( AddStaticName, STATIC:FindByName( AddStaticName ) ) + end + + return self + end + + --- Remove STATIC(s) from SET_STATIC. + -- @param Core.Set#SET_STATIC self + -- @param Wrapper.Static#STATIC RemoveStaticNames A single name or an array of STATIC names. + -- @return self + function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) + + local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } + + for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do + self:Remove( RemoveStaticName ) + end + + return self + end + + + --- Finds a Static based on the Static Name. + -- @param #SET_STATIC self + -- @param #string StaticName + -- @return Wrapper.Static#STATIC The found Static. + function SET_STATIC:FindStatic( StaticName ) + + local StaticFound = self.Set[StaticName] + return StaticFound + end + + + + --- Builds a set of units of coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_STATIC self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". + -- @return #SET_STATIC self + function SET_STATIC:FilterCoalitions( Coalitions ) + if not self.Filter.Coalitions then + self.Filter.Coalitions = {} + end + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } + end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self + end + + + --- Builds a set of units out of categories. + -- Possible current categories are plane, helicopter, ground, ship. + -- @param #SET_STATIC self + -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". + -- @return #SET_STATIC self + function SET_STATIC:FilterCategories( Categories ) + if not self.Filter.Categories then + self.Filter.Categories = {} + end + if type( Categories ) ~= "table" then + Categories = { Categories } + end + for CategoryID, Category in pairs( Categories ) do + self.Filter.Categories[Category] = Category + end + return self + end + + + --- Builds a set of units of defined unit types. + -- Possible current types are those types known within DCS world. + -- @param #SET_STATIC self + -- @param #string Types Can take those type strings known within DCS world. + -- @return #SET_STATIC self + function SET_STATIC:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self + end + + + --- Builds a set of units of defined countries. + -- Possible current countries are those known within DCS world. + -- @param #SET_STATIC self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_STATIC self + function SET_STATIC:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self + end + + + --- Builds a set of units of defined unit prefixes. + -- All the units starting with the given prefixes will be included within the set. + -- @param #SET_STATIC self + -- @param #string Prefixes The prefix of which the unit name starts with. + -- @return #SET_STATIC self + function SET_STATIC:FilterPrefixes( Prefixes ) + if not self.Filter.StaticPrefixes then + self.Filter.StaticPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.StaticPrefixes[Prefix] = Prefix + end + return self + end + + + --- Starts the filtering. + -- @param #SET_STATIC self + -- @return #SET_STATIC self + function SET_STATIC:FilterStart() + + if _DATABASE then + self:_FilterStart() + end + + return self + end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. + -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! + -- @param #SET_STATIC self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the STATIC + -- @return #table The STATIC + function SET_STATIC:AddInDatabase( Event ) + self:F3( { Event } ) + + if Event.IniObjectCategory == Object.Category.STATIC then + if not self.Database[Event.IniDCSStaticName] then + self.Database[Event.IniDCSStaticName] = STATIC:Register( Event.IniDCSStaticName ) + self:T3( self.Database[Event.IniDCSStaticName] ) + end + end + + return Event.IniDCSStaticName, self.Database[Event.IniDCSStaticName] + end + + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! + -- @param #SET_STATIC self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the STATIC + -- @return #table The STATIC + function SET_STATIC:FindInDatabase( Event ) + self:F2( { Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName], Event } ) + + + return Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName] + end + + + do -- Is Zone methods + + --- Check if minimal one element of the SET_STATIC is in the Zone. + -- @param #SET_STATIC self + -- @param Core.Zone#ZONE Zone The Zone to be tested for. + -- @return #boolean + function SET_STATIC:IsPatriallyInZone( Zone ) + + local IsPartiallyInZone = false + + local function EvaluateZone( ZoneStatic ) + + local ZoneStaticName = ZoneStatic:GetName() + if self:FindStatic( ZoneStaticName ) then + IsPartiallyInZone = true + return false + end + + return true + end + + return IsPartiallyInZone + end + + + --- Check if no element of the SET_STATIC is in the Zone. + -- @param #SET_STATIC self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @return #boolean + function SET_STATIC:IsNotInZone( Zone ) + + local IsNotInZone = true + + local function EvaluateZone( ZoneStatic ) + + local ZoneStaticName = ZoneStatic:GetName() + if self:FindStatic( ZoneStaticName ) then + IsNotInZone = false + return false + end + + return true + end + + Zone:Search( EvaluateZone ) + + return IsNotInZone + end + + + --- Check if minimal one element of the SET_STATIC is in the Zone. + -- @param #SET_STATIC self + -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. + -- @return #SET_STATIC self + function SET_STATIC:ForEachStaticInZone( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self + end + + + end + + + --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters. + -- @param #SET_STATIC self + -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. + -- @return #SET_STATIC self + function SET_STATIC:ForEachStatic( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self + end + + + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- @param #SET_STATIC self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. + -- @return #SET_STATIC self + function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self + end + + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- @param #SET_STATIC self + -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. + -- @return #SET_STATIC self + function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self + end + + --- Returns map of unit types. + -- @param #SET_STATIC self + -- @return #map<#string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found. + function SET_STATIC:GetStaticTypes() + self:F2() + + local MT = {} -- Message Text + local StaticTypes = {} + + for StaticID, StaticData in pairs( self:GetSet() ) do + local TextStatic = StaticData -- Wrapper.Static#STATIC + if TextStatic:IsAlive() then + local StaticType = TextStatic:GetTypeName() + + if not StaticTypes[StaticType] then + StaticTypes[StaticType] = 1 + else + StaticTypes[StaticType] = StaticTypes[StaticType] + 1 + end + end + end + + for StaticTypeID, StaticType in pairs( StaticTypes ) do + MT[#MT+1] = StaticType .. " of " .. StaticTypeID + end + + return StaticTypes + end + + + --- Returns a comma separated string of the unit types with a count in the @{Set}. + -- @param #SET_STATIC self + -- @return #string The unit types string + function SET_STATIC:GetStaticTypesText() + self:F2() + + local MT = {} -- Message Text + local StaticTypes = self:GetStaticTypes() + + for StaticTypeID, StaticType in pairs( StaticTypes ) do + MT[#MT+1] = StaticType .. " of " .. StaticTypeID + end + + return table.concat( MT, ", " ) + end + + --- Get the center coordinate of the SET_STATIC. + -- @param #SET_STATIC self + -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units. + function SET_STATIC:GetCoordinate() + + local Coordinate = self:GetFirst():GetCoordinate() + + local x1 = Coordinate.x + local x2 = Coordinate.x + local y1 = Coordinate.y + local y2 = Coordinate.y + local z1 = Coordinate.z + local z2 = Coordinate.z + local MaxVelocity = 0 + local AvgHeading = nil + local MovingCount = 0 + + for StaticName, StaticData in pairs( self:GetSet() ) do + + local Static = StaticData -- Wrapper.Static#STATIC + local Coordinate = Static:GetCoordinate() + + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 + x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 + y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 + y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 + z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 + z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + + local Velocity = Coordinate:GetVelocity() + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + local Heading = Coordinate:GetHeading() + AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + MovingCount = MovingCount + 1 + end + end + + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + + Coordinate.x = ( x2 - x1 ) / 2 + x1 + Coordinate.y = ( y2 - y1 ) / 2 + y1 + Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate:SetHeading( AvgHeading ) + Coordinate:SetVelocity( MaxVelocity ) + + self:F( { Coordinate = Coordinate } ) + return Coordinate + + end + + --- Get the maximum velocity of the SET_STATIC. + -- @param #SET_STATIC self + -- @return #number The speed in mps in case of moving units. + function SET_STATIC:GetVelocity() + + return 0 + + end + + --- Get the average heading of the SET_STATIC. + -- @param #SET_STATIC self + -- @return #number Heading Heading in degrees and speed in mps in case of moving units. + function SET_STATIC:GetHeading() + + local HeadingSet = nil + local MovingCount = 0 + + for StaticName, StaticData in pairs( self:GetSet() ) do + + local Static = StaticData -- Wrapper.Static#STATIC + local Coordinate = Static:GetCoordinate() + + local Velocity = Coordinate:GetVelocity() + if Velocity ~= 0 then + local Heading = Coordinate:GetHeading() + if HeadingSet == nil then + HeadingSet = Heading + else + local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + HeadingDiff = math.abs( HeadingDiff ) + if HeadingDiff > 5 then + HeadingSet = nil + break + end + end + end + end + + return HeadingSet + + end + + + --- + -- @param #SET_STATIC self + -- @param Wrapper.Static#STATIC MStatic + -- @return #SET_STATIC self + function SET_STATIC:IsIncludeObject( MStatic ) + self:F2( MStatic ) + local MStaticInclude = true + + if self.Filter.Coalitions then + local MStaticCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + self:T3( { "Coalition:", MStatic:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MStatic:GetCoalition() then + MStaticCoalition = true + end + end + MStaticInclude = MStaticInclude and MStaticCoalition + end + + if self.Filter.Categories then + local MStaticCategory = false + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + self:T3( { "Category:", MStatic:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MStatic:GetDesc().category then + MStaticCategory = true + end + end + MStaticInclude = MStaticInclude and MStaticCategory + end + + if self.Filter.Types then + local MStaticType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + self:T3( { "Type:", MStatic:GetTypeName(), TypeName } ) + if TypeName == MStatic:GetTypeName() then + MStaticType = true + end + end + MStaticInclude = MStaticInclude and MStaticType + end + + if self.Filter.Countries then + local MStaticCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + self:T3( { "Country:", MStatic:GetCountry(), CountryName } ) + if country.id[CountryName] == MStatic:GetCountry() then + MStaticCountry = true + end + end + MStaticInclude = MStaticInclude and MStaticCountry + end + + if self.Filter.StaticPrefixes then + local MStaticPrefix = false + for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do + self:T3( { "Prefix:", string.find( MStatic:GetName(), StaticPrefix, 1 ), StaticPrefix } ) + if string.find( MStatic:GetName(), StaticPrefix, 1 ) then + MStaticPrefix = true + end + end + MStaticInclude = MStaticInclude and MStaticPrefix + end + + self:T2( MStaticInclude ) + return MStaticInclude + end + + + --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter. + -- @param #SET_STATIC self + -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @return #string The types of the @{Static}s delimited. + function SET_STATIC:GetTypeNames( Delimiter ) + + Delimiter = Delimiter or ", " + local TypeReport = REPORT:New() + local Types = {} + + for StaticName, StaticData in pairs( self:GetSet() ) do + + local Static = StaticData -- Wrapper.Static#STATIC + local StaticTypeName = Static:GetTypeName() + + if not Types[StaticTypeName] then + Types[StaticTypeName] = StaticTypeName + TypeReport:Add( StaticTypeName ) + end + end + + return TypeReport:Text( Delimiter ) end - return TypeReport:Text( Delimiter ) end @@ -2421,6 +3326,405 @@ function SET_CLIENT:IsIncludeObject( MClient ) return MClientInclude end +--- SET_PLAYER + + +--- @type SET_PLAYER +-- @extends Core.Set#SET_BASE + + + +--- # 4) SET_PLAYER class, extends @{Set#SET_BASE} +-- +-- Mission designers can use the @{Set#SET_PLAYER} class to build sets of units belonging to alive players: +-- +-- ## 4.1) SET_PLAYER constructor +-- +-- Create a new SET_PLAYER object with the @{#SET_PLAYER.New} method: +-- +-- * @{#SET_PLAYER.New}: Creates a new SET_PLAYER object. +-- +-- ## 4.3) SET_PLAYER filter criteria +-- +-- You can set filter criteria to define the set of clients within the SET_PLAYER. +-- Filter criteria are defined by: +-- +-- * @{#SET_PLAYER.FilterCoalitions}: Builds the SET_PLAYER with the clients belonging to the coalition(s). +-- * @{#SET_PLAYER.FilterCategories}: Builds the SET_PLAYER with the clients belonging to the category(ies). +-- * @{#SET_PLAYER.FilterTypes}: Builds the SET_PLAYER with the clients belonging to the client type(s). +-- * @{#SET_PLAYER.FilterCountries}: Builds the SET_PLAYER with the clients belonging to the country(ies). +-- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients starting with the same prefix string(s). +-- +-- Once the filter criteria have been set for the SET_PLAYER, you can start filtering using: +-- +-- * @{#SET_PLAYER.FilterStart}: Starts the filtering of the clients within the SET_PLAYER. +-- +-- Planned filter criteria within development are (so these are not yet available): +-- +-- * @{#SET_PLAYER.FilterZones}: Builds the SET_PLAYER with the clients within a @{Zone#ZONE}. +-- +-- ## 4.4) SET_PLAYER iterators +-- +-- Once the filters have been defined and the SET_PLAYER has been built, you can iterate the SET_PLAYER with the available iterator methods. +-- The iterator methods will walk the SET_PLAYER set, and call for each element within the set a function that you provide. +-- The following iterator methods are currently available within the SET_PLAYER: +-- +-- * @{#SET_PLAYER.ForEachClient}: Calls a function for each alive client it finds within the SET_PLAYER. +-- +-- === +-- @field #SET_PLAYER SET_PLAYER +SET_PLAYER = { + ClassName = "SET_PLAYER", + Clients = {}, + Filter = { + Coalitions = nil, + Categories = nil, + Types = nil, + Countries = nil, + ClientPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Unit.Category.AIRPLANE, + helicopter = Unit.Category.HELICOPTER, + ground = Unit.Category.GROUND_UNIT, + ship = Unit.Category.SHIP, + structure = Unit.Category.STRUCTURE, + }, + }, +} + + +--- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. +-- @param #SET_PLAYER self +-- @return #SET_PLAYER +-- @usage +-- -- Define a new SET_PLAYER Object. This DBObject will contain a reference to all Clients. +-- DBObject = SET_PLAYER:New() +function SET_PLAYER:New() + -- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.PLAYERS ) ) + + return self +end + +--- Add CLIENT(s) to SET_PLAYER. +-- @param Core.Set#SET_PLAYER self +-- @param #string AddClientNames A single name or an array of CLIENT names. +-- @return self +function SET_PLAYER:AddClientsByName( AddClientNames ) + + local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + + for AddClientID, AddClientName in pairs( AddClientNamesArray ) do + self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) + end + + return self +end + +--- Remove CLIENT(s) from SET_PLAYER. +-- @param Core.Set#SET_PLAYER self +-- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. +-- @return self +function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) + + local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + + for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do + self:Remove( RemoveClientName.ClientName ) + end + + return self +end + + +--- Finds a Client based on the Player Name. +-- @param #SET_PLAYER self +-- @param #string PlayerName +-- @return Wrapper.Client#CLIENT The found Client. +function SET_PLAYER:FindClient( PlayerName ) + + local ClientFound = self.Set[PlayerName] + return ClientFound +end + + + +--- Builds a set of clients of coalitions joined by specific players. +-- Possible current coalitions are red, blue and neutral. +-- @param #SET_PLAYER self +-- @param #string Coalitions Can take the following values: "red", "blue", "neutral". +-- @return #SET_PLAYER self +function SET_PLAYER:FilterCoalitions( Coalitions ) + if not self.Filter.Coalitions then + self.Filter.Coalitions = {} + end + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } + end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self +end + + +--- Builds a set of clients out of categories joined by players. +-- Possible current categories are plane, helicopter, ground, ship. +-- @param #SET_PLAYER self +-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". +-- @return #SET_PLAYER self +function SET_PLAYER:FilterCategories( Categories ) + if not self.Filter.Categories then + self.Filter.Categories = {} + end + if type( Categories ) ~= "table" then + Categories = { Categories } + end + for CategoryID, Category in pairs( Categories ) do + self.Filter.Categories[Category] = Category + end + return self +end + + +--- Builds a set of clients of defined client types joined by players. +-- Possible current types are those types known within DCS world. +-- @param #SET_PLAYER self +-- @param #string Types Can take those type strings known within DCS world. +-- @return #SET_PLAYER self +function SET_PLAYER:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self +end + + +--- Builds a set of clients of defined countries. +-- Possible current countries are those known within DCS world. +-- @param #SET_PLAYER self +-- @param #string Countries Can take those country strings known within DCS world. +-- @return #SET_PLAYER self +function SET_PLAYER:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self +end + + +--- Builds a set of clients of defined client prefixes. +-- All the clients starting with the given prefixes will be included within the set. +-- @param #SET_PLAYER self +-- @param #string Prefixes The prefix of which the client name starts with. +-- @return #SET_PLAYER self +function SET_PLAYER:FilterPrefixes( Prefixes ) + if not self.Filter.ClientPrefixes then + self.Filter.ClientPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.ClientPrefixes[Prefix] = Prefix + end + return self +end + + + + +--- Starts the filtering. +-- @param #SET_PLAYER self +-- @return #SET_PLAYER self +function SET_PLAYER:FilterStart() + + if _DATABASE then + self:_FilterStart() + end + + return self +end + +--- Handles the Database to check on an event (birth) that the Object was added in the Database. +-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! +-- @param #SET_PLAYER self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the CLIENT +-- @return #table The CLIENT +function SET_PLAYER:AddInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Handles the Database to check on any event that Object exists in the Database. +-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! +-- @param #SET_PLAYER self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the CLIENT +-- @return #table The CLIENT +function SET_PLAYER:FindInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Iterate the SET_PLAYER and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. +-- @param #SET_PLAYER self +-- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. +-- @return #SET_PLAYER self +function SET_PLAYER:ForEachPlayer( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self +end + +--- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. +-- @param #SET_PLAYER self +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. +-- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. +-- @return #SET_PLAYER self +function SET_PLAYER:ForEachPlayerInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self +end + +--- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. +-- @param #SET_PLAYER self +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. +-- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. +-- @return #SET_PLAYER self +function SET_PLAYER:ForEachPlayerNotInZone( ZoneObject, IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set, + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) + + return self +end + +--- +-- @param #SET_PLAYER self +-- @param Wrapper.Client#CLIENT MClient +-- @return #SET_PLAYER self +function SET_PLAYER:IsIncludeObject( MClient ) + self:F2( MClient ) + + local MClientInclude = true + + if MClient then + local MClientName = MClient.UnitName + + if self.Filter.Coalitions then + local MClientCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + local ClientCoalitionID = _DATABASE:GetCoalitionFromClientTemplate( MClientName ) + self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then + MClientCoalition = true + end + end + self:T( { "Evaluated Coalition", MClientCoalition } ) + MClientInclude = MClientInclude and MClientCoalition + end + + if self.Filter.Categories then + local MClientCategory = false + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) + self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then + MClientCategory = true + end + end + self:T( { "Evaluated Category", MClientCategory } ) + MClientInclude = MClientInclude and MClientCategory + end + + if self.Filter.Types then + local MClientType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) + if TypeName == MClient:GetTypeName() then + MClientType = true + end + end + self:T( { "Evaluated Type", MClientType } ) + MClientInclude = MClientInclude and MClientType + end + + if self.Filter.Countries then + local MClientCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) + if country.id[CountryName] and country.id[CountryName] == ClientCountryID then + MClientCountry = true + end + end + self:T( { "Evaluated Country", MClientCountry } ) + MClientInclude = MClientInclude and MClientCountry + end + + if self.Filter.ClientPrefixes then + local MClientPrefix = false + for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do + self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) + if string.find( MClient.UnitName, ClientPrefix, 1 ) then + MClientPrefix = true + end + end + self:T( { "Evaluated Prefix", MClientPrefix } ) + MClientInclude = MClientInclude and MClientPrefix + end + end + + self:T2( MClientInclude ) + return MClientInclude +end + --- @type SET_AIRBASE -- @extends Core.Set#SET_BASE @@ -2748,13 +4052,13 @@ SET_CARGO = { --- (R2.1) Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self --- @return #SET_CARGO self +-- @return #SET_CARGO -- @usage -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. -- DatabaseSet = SET_CARGO:New() function SET_CARGO:New() --R2.1 -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO return self end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index dc0926ba5..b584947bc 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -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 diff --git a/Moose Development/Moose/Functional/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua similarity index 85% rename from Moose Development/Moose/Functional/Spawn.lua rename to Moose Development/Moose/Core/Spawn.lua index 8ad718194..b16e1bd79 100644 --- a/Moose Development/Moose/Functional/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -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 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 diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 0d2b01109..533c51e1f 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -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 diff --git a/Moose Development/Moose/Core/Spot.lua b/Moose Development/Moose/Core/Spot.lua index 67c8a83db..5e6eb014f 100644 --- a/Moose Development/Moose/Core/Spot.lua +++ b/Moose Development/Moose/Core/Spot.lua @@ -222,7 +222,7 @@ do self:HandleEvent( EVENTS.Dead ) - self:__Lasing( -0.2 ) + self:__Lasing( -1 ) end --- @param #SPOT self diff --git a/Moose Development/Moose/Core/UserFlag.lua b/Moose Development/Moose/Core/UserFlag.lua new file mode 100644 index 000000000..3c11718da --- /dev/null +++ b/Moose Development/Moose/Core/UserFlag.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Core/UserSound.lua b/Moose Development/Moose/Core/UserSound.lua new file mode 100644 index 000000000..ff1c316c8 --- /dev/null +++ b/Moose Development/Moose/Core/UserSound.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Core/Velocity.lua b/Moose Development/Moose/Core/Velocity.lua new file mode 100644 index 000000000..964d67577 --- /dev/null +++ b/Moose Development/Moose/Core/Velocity.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 5d183f58d..a47761cb9 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -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 + diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua new file mode 100644 index 000000000..b27fa27f4 --- /dev/null +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -0,0 +1,2311 @@ +--- **Functional** -- The ATC\_GROUND classes monitor airbase traffic and regulate speed while taxiing. +-- +-- === +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- === +-- +-- ### Contributions: Dutch Baron - Concept & Testing +-- ### Author: FlightControl - Framework Design & Programming +-- +-- === +-- +-- @module ATC_Ground + + +--- @type ATC_GROUND +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Base#BASE + +--- Base class for ATC\_GROUND implementations. +-- @field #ATC_GROUND +ATC_GROUND = { + ClassName = "ATC_GROUND", + SetClient = nil, + Airbases = nil, + AirbaseNames = nil, + --KickSpeed = nil, -- The maximum speed in meters per second for all airbases until a player gets kicked. This is overridden at each derived class. +} + +--- @type ATC_GROUND.AirbaseNames +-- @list <#string> + + +--- Creates a new ATC\_GROUND object. +-- @param #ATC_GROUND self +-- @param Airbases A table of Airbase Names. +-- @return #ATC_GROUND self +function ATC_GROUND:New( Airbases, AirbaseList ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND + self:E( { self.ClassName, Airbases } ) + + self.Airbases = Airbases + self.AirbaseList = AirbaseList + + self.SetClient = SET_CLIENT:New():FilterCategories( "plane" ):FilterStart() + + + for AirbaseID, Airbase in pairs( self.Airbases ) do + Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + Airbase.ZoneRunways = {} + for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do + Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ) + end + Airbase.Monitor = self.AirbaseList and false or true -- When AirbaseList is not given, monitor every Airbase, otherwise don't monitor any (yet). + end + + -- Now activate the monitoring for the airbases that need to be monitored. + for AirbaseID, AirbaseName in pairs( self.AirbaseList or {} ) do + self.Airbases[AirbaseName].Monitor = true + end + +-- -- Template +-- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) +-- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) +-- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + function( Client ) + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0) + Client:SetState( self, "IsOffRunway", false ) + Client:SetState( self, "OffRunwayWarnings", 0 ) + Client:SetState( self, "Taxi", false ) + end + ) + + -- This is simple slot blocker is used on the server. + SSB = USERFLAG:New( "SSB" ) + SSB:Set( 100 ) + + return self +end + + +--- Smoke the airbases runways. +-- @param #ATC_GROUND self +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke around the runways. +-- @return #ATC_GROUND self +function ATC_GROUND:SmokeRunways( SmokeColor ) + + for AirbaseID, Airbase in pairs( self.Airbases ) do + for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do + Airbase.ZoneRunways[PointsRunwayID]:SmokeZone( SmokeColor ) + end + end +end + + +--- Set the maximum speed in meters per second (Mps) until the player gets kicked. +-- An airbase can be specified to set the kick speed for. +-- @param #ATC_GROUND self +-- @param #number KickSpeed The speed in Mps. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- @usage +-- +-- -- Declare Atc_Ground using one of those, depending on the map. +-- +-- Atc_Ground = ATC_GROUND_CAUCAUS:New() +-- Atc_Ground = ATC_GROUND_NEVADA:New() +-- Atc_Ground = ATC_GROUND_NORMANDY:New() +-- +-- -- Then use one of these methods... +-- +-- Atc_Ground:SetKickSpeed( UTILS.KmphToMps( 80 ) ) -- Kick the players at 80 kilometers per hour +-- +-- Atc_Ground:SetKickSpeed( UTILS.MiphToMps( 100 ) ) -- Kick the players at 100 miles per hour +-- +-- Atc_Ground:SetKickSpeed( 24 ) -- Kick the players at 24 meters per second ( 24 * 3.6 = 86.4 kilometers per hour ) +-- +function ATC_GROUND:SetKickSpeed( KickSpeed, Airbase ) + + if not Airbase then + self.KickSpeed = KickSpeed + else + self.Airbases[Airbase].KickSpeed = KickSpeed + end + + return self +end + +--- Set the maximum speed in Kmph until the player gets kicked. +-- @param #ATC_GROUND self +-- @param #number KickSpeed Set the speed in Kmph. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- +-- Atc_Ground:SetKickSpeedKmph( 80 ) -- Kick the players at 80 kilometers per hour +-- +function ATC_GROUND:SetKickSpeedKmph( KickSpeed, Airbase ) + + self:SetKickSpeed( UTILS.KmphToMps( KickSpeed ), Airbase ) + + return self +end + +--- Set the maximum speed in Miph until the player gets kicked. +-- @param #ATC_GROUND self +-- @param #number KickSpeedMiph Set the speed in Mph. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- +-- Atc_Ground:SetKickSpeedMiph( 100 ) -- Kick the players at 100 miles per hour +-- +function ATC_GROUND:SetKickSpeedMiph( KickSpeedMiph, Airbase ) + + self:SetKickSpeed( UTILS.MiphToMps( KickSpeedMiph ), Airbase ) + + return self +end + + +--- Set the maximum kick speed in meters per second (Mps) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND self +-- @param #number MaximumKickSpeed The speed in Mps. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- @usage +-- +-- -- Declare Atc_Ground using one of those, depending on the map. +-- +-- Atc_Ground = ATC_GROUND_CAUCAUS:New() +-- Atc_Ground = ATC_GROUND_NEVADA:New() +-- Atc_Ground = ATC_GROUND_NORMANDY:New() +-- +-- -- Then use one of these methods... +-- +-- Atc_Ground:SetMaximumKickSpeed( UTILS.KmphToMps( 80 ) ) -- Kick the players at 80 kilometers per hour +-- +-- Atc_Ground:SetMaximumKickSpeed( UTILS.MiphToMps( 100 ) ) -- Kick the players at 100 miles per hour +-- +-- Atc_Ground:SetMaximumKickSpeed( 24 ) -- Kick the players at 24 meters per second ( 24 * 3.6 = 86.4 kilometers per hour ) +-- +function ATC_GROUND:SetMaximumKickSpeed( MaximumKickSpeed, Airbase ) + + if not Airbase then + self.MaximumKickSpeed = MaximumKickSpeed + else + self.Airbases[Airbase].MaximumKickSpeed = MaximumKickSpeed + end + + return self +end + +--- Set the maximum kick speed in kilometers per hour (Kmph) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND self +-- @param #number MaximumKickSpeed Set the speed in Kmph. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- +-- Atc_Ground:SetMaximumKickSpeedKmph( 150 ) -- Kick the players at 150 kilometers per hour +-- +function ATC_GROUND:SetMaximumKickSpeedKmph( MaximumKickSpeed, Airbase ) + + self:SetMaximumKickSpeed( UTILS.KmphToMps( MaximumKickSpeed ), Airbase ) + + return self +end + +--- Set the maximum kick speed in miles per hour (Miph) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND self +-- @param #number MaximumKickSpeedMiph Set the speed in Mph. +-- @param Wrapper.Airbase#AIRBASE Airbase (optional) The airbase to set the kick speed for. +-- @return #ATC_GROUND self +-- +-- Atc_Ground:SetMaximumKickSpeedMiph( 100 ) -- Kick the players at 100 miles per hour +-- +function ATC_GROUND:SetMaximumKickSpeedMiph( MaximumKickSpeedMiph, Airbase ) + + self:SetMaximumKickSpeed( UTILS.MiphToMps( MaximumKickSpeedMiph ), Airbase ) + + return self +end + + +--- @param #ATC_GROUND self +function ATC_GROUND:_AirbaseMonitor() + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + function( Client ) + + if Client:IsAlive() then + + local IsOnGround = Client:InAir() == false + + for AirbaseID, AirbaseMeta in pairs( self.Airbases ) do + self:E( AirbaseID, AirbaseMeta.KickSpeed ) + + if AirbaseMeta.Monitor == true and Client:IsInZone( AirbaseMeta.ZoneBoundary ) then + + local NotInRunwayZone = true + for ZoneRunwayID, ZoneRunway in pairs( AirbaseMeta.ZoneRunways ) do + NotInRunwayZone = ( Client:IsNotInZone( ZoneRunway ) == true ) and NotInRunwayZone or false + end + + if NotInRunwayZone then + + if IsOnGround then + local Taxi = Client:GetState( self, "Taxi" ) + self:E( Taxi ) + if Taxi == false then + local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed ) + Client:Message( "Welcome at " .. AirbaseID .. ". The maximum taxiing speed is " .. + Velocity:ToString() , 20, "ATC" ) + Client:SetState( self, "Taxi", true ) + end + + -- TODO: GetVelocityKMH function usage + local Velocity = VELOCITY_POSITIONABLE:New( Client ) + --MESSAGE:New( "Velocity = " .. Velocity:ToString(), 1 ):ToAll() + local IsAboveRunway = Client:IsAboveRunway() + self:T( IsAboveRunway, IsOnGround ) + + if IsOnGround then + local Speeding = false + if AirbaseMeta.MaximumKickSpeed then + if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then + Speeding = true + end + else + if Velocity:Get() > self.MaximumKickSpeed then + Speeding = true + end + end + if Speeding == true then + MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. + " is kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + Client:Destroy() + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) + end + end + + + if IsOnGround then + + local Speeding = false + if AirbaseMeta.KickSpeed then -- If there is a speed defined for the airbase, use that only. + if Velocity:Get() > AirbaseMeta.KickSpeed then + Speeding = true + end + else + if Velocity:Get() > self.KickSpeed then + Speeding = true + end + end + if Speeding == true then + local IsSpeeding = Client:GetState( self, "Speeding" ) + + if IsSpeeding == true then + local SpeedingWarnings = Client:GetState( self, "Warnings" ) + self:T( SpeedingWarnings ) + + if SpeedingWarnings <= 3 then + Client:Message( "Warning " .. SpeedingWarnings .. "/3! Airbase traffic rule violation! Slow down now! Your speed is " .. + Velocity:ToString(), 5, "ATC" ) + Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) + else + MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " is kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + --- @param Wrapper.Client#CLIENT Client + Client:Destroy() + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) + end + + else + Client:Message( "Attention! You are speeding on the taxiway, slow down! Your speed is " .. + Velocity:ToString(), 5, "ATC" ) + Client:SetState( self, "Speeding", true ) + Client:SetState( self, "Warnings", 1 ) + end + + else + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) + end + end + + if IsOnGround and not IsAboveRunway then + + local IsOffRunway = Client:GetState( self, "IsOffRunway" ) + + if IsOffRunway == true then + local OffRunwayWarnings = Client:GetState( self, "OffRunwayWarnings" ) + self:T( OffRunwayWarnings ) + + if OffRunwayWarnings <= 3 then + Client:Message( "Warning " .. OffRunwayWarnings .. "/3! Airbase traffic rule violation! Get back on the taxi immediately!", 5, "ATC" ) + Client:SetState( self, "OffRunwayWarnings", OffRunwayWarnings + 1 ) + else + MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " is kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + --- @param Wrapper.Client#CLIENT Client + Client:Destroy() + Client:SetState( self, "IsOffRunway", false ) + Client:SetState( self, "OffRunwayWarnings", 0 ) + end + else + Client:Message( "Attention! You are off the taxiway. Get back on the taxiway immediately!", 5, "ATC" ) + Client:SetState( self, "IsOffRunway", true ) + Client:SetState( self, "OffRunwayWarnings", 1 ) + end + + else + Client:SetState( self, "IsOffRunway", false ) + Client:SetState( self, "OffRunwayWarnings", 0 ) + end + end + else + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) + Client:SetState( self, "IsOffRunway", false ) + Client:SetState( self, "OffRunwayWarnings", 0 ) + local Taxi = Client:GetState( self, "Taxi" ) + if Taxi == true then + Client:Message( "You have progressed to the runway ... Await take-off clearance ...", 20, "ATC" ) + Client:SetState( self, "Taxi", false ) + end + end + end + end + else + Client:SetState( self, "Taxi", false ) + end + end + ) + + return true +end + + +--- @type ATC_GROUND_CAUCASUS +-- @extends #ATC_GROUND + +--- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_CAUCASUS class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Caucasus is **50 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way. +-- +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Caucasus region. +-- Use the @{Airbase#AIRBASE.Caucasus} enumeration to select the airbases to be monitored. +-- +-- * `AIRBASE.Caucasus.Anapa_Vityazevo` +-- * `AIRBASE.Caucasus.Batumi` +-- * `AIRBASE.Caucasus.Beslan` +-- * `AIRBASE.Caucasus.Gelendzhik` +-- * `AIRBASE.Caucasus.Gudauta` +-- * `AIRBASE.Caucasus.Kobuleti` +-- * `AIRBASE.Caucasus.Krasnodar_Center` +-- * `AIRBASE.Caucasus.Krasnodar_Pashkovsky` +-- * `AIRBASE.Caucasus.Krymsk` +-- * `AIRBASE.Caucasus.Kutaisi` +-- * `AIRBASE.Caucasus.Maykop_Khanskaya` +-- * `AIRBASE.Caucasus.Mineralnye_Vody` +-- * `AIRBASE.Caucasus.Mozdok` +-- * `AIRBASE.Caucasus.Nalchik` +-- * `AIRBASE.Caucasus.Novorossiysk` +-- * `AIRBASE.Caucasus.Senaki_Kolkhi` +-- * `AIRBASE.Caucasus.Sochi_Adler` +-- * `AIRBASE.Caucasus.Soganlug` +-- * `AIRBASE.Caucasus.Sukhumi_Babushara` +-- * `AIRBASE.Caucasus.Tbilisi_Lochini` +-- * `AIRBASE.Caucasus.Vaziani` +-- +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC\_GROUND\_CAUCASUS Constructor +-- +-- Creates a new ATC_GROUND_CAUCASUS object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_CAUCASUS object. +-- +-- -- Monitor all the airbases. +-- ATC_Ground = ATC_GROUND_CAUCASUS:New() +-- +-- -- Monitor specific airbases only. +-- +-- ATC_Ground = ATC_GROUND_CAUCASUS:New( +-- { AIRBASE.Caucasus.Gelendzhik, +-- AIRBASE.Caucasus.Krymsk +-- } +-- ) +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +-- +-- @field #ATC_GROUND_CAUCASUS +ATC_GROUND_CAUCASUS = { + ClassName = "ATC_GROUND_CAUCASUS", + Airbases = { + [AIRBASE.Caucasus.Anapa_Vityazevo] = { + PointsRunways = { + [1] = { + [1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, + [2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, + [3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, + [4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, + [5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} + }, + }, + }, + [AIRBASE.Caucasus.Batumi] = { + PointsRunways = { + [1] = { + [1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, + [2]={["y"]=618450.57142857,["x"]=-356522,}, + [3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, + [4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, + [5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, + [6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, + [7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, + [8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, + [9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, + [10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, + [11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, + [12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, + [13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, + [14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, + }, + }, + }, + [AIRBASE.Caucasus.Beslan] = { + PointsRunways = { + [1] = { + [1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, + [2]={["y"]=845225.71428572,["x"]=-148656,}, + [3]={["y"]=845220.57142858,["x"]=-148750,}, + [4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, + [5]={["y"]=842104,["x"]=-148460.28571429,}, + }, + }, + }, + [AIRBASE.Caucasus.Gelendzhik] = { + PointsRunways = { + [1] = { + [1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, + [2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, + [3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, + [4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, + [5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, + }, + }, + }, + [AIRBASE.Caucasus.Gudauta] = { + PointsRunways = { + [1] = { + [1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, + [2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, + [3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, + [4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, + [5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, + }, + }, + }, + [AIRBASE.Caucasus.Kobuleti] = { + PointsRunways = { + [1] = { + [1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, + [2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, + [3]={["y"]=636790,["x"]=-317575.71428572,}, + [4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, + [5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, + }, + }, + }, + [AIRBASE.Caucasus.Krasnodar_Center] = { + PointsRunways = { + [1] = { + [1]={["y"]=369205.42857144,["x"]=11789.142857142,}, + [2]={["y"]=369209.71428572,["x"]=11714.857142856,}, + [3]={["y"]=366699.71428572,["x"]=11581.714285713,}, + [4]={["y"]=366698.28571429,["x"]=11659.142857142,}, + [5]={["y"]=369208.85714286,["x"]=11788.57142857,}, + }, + }, + }, + [AIRBASE.Caucasus.Krasnodar_Pashkovsky] = { + PointsRunways = { + [1] = { + [1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, + [2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, + [3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, + [4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, + [5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, + }, + [2] = { + [1]={["y"]=386714.85714286,["x"]=6674.857142856,}, + [2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, + [3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, + [4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, + [5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, + }, + }, + }, + [AIRBASE.Caucasus.Krymsk] = { + PointsRunways = { + [1] = { + [1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, + [2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, + [3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, + [4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, + [5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, + }, + }, + }, + [AIRBASE.Caucasus.Kutaisi] = { + PointsRunways = { + [1] = { + [1]={["y"]=682638,["x"]=-285202.28571429,}, + [2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, + [3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, + [4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, + [5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, + }, + }, + }, + [AIRBASE.Caucasus.Maykop_Khanskaya] = { + PointsRunways = { + [1] = { + [1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, + [2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, + [3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, + [4]={["y"]=457060,["x"]=-27714.285714287,}, + [5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, + }, + }, + }, + [AIRBASE.Caucasus.Mineralnye_Vody] = { + PointsRunways = { + [1] = { + [1]={["y"]=703904,["x"]=-50352.571428573,}, + [2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, + [3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, + [4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, + [5]={["y"]=703902,["x"]=-50352.000000002,}, + }, + }, + }, + [AIRBASE.Caucasus.Mozdok] = { + PointsRunways = { + [1] = { + [1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, + [2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, + [3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, + [4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, + [5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, + }, + }, + }, + [AIRBASE.Caucasus.Nalchik] = { + PointsRunways = { + [1] = { + [1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, + [2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, + [3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, + [4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, + [5]={["y"]=759456,["x"]=-125552.57142857,}, + }, + }, + }, + [AIRBASE.Caucasus.Novorossiysk] = { + PointsRunways = { + [1] = { + [1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, + [2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, + [3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, + [4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, + [5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, + }, + }, + }, + [AIRBASE.Caucasus.Senaki_Kolkhi] = { + PointsRunways = { + [1] = { + [1]={["y"]=646060.85714285,["x"]=-281736,}, + [2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, + [3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, + [4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, + [5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, + }, + }, + }, + [AIRBASE.Caucasus.Sochi_Adler] = { + PointsRunways = { + [1] = { + [1]={["y"]=460831.42857143,["x"]=-165180,}, + [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, + [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, + [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, + [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, + }, + [2] = { + [1]={["y"]=460831.42857143,["x"]=-165180,}, + [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, + [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, + [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, + [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, + }, + }, + }, + [AIRBASE.Caucasus.Soganlug] = { + PointsRunways = { + [1] = { + [1]={["y"]=894525.71428571,["x"]=-316964,}, + [2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, + [3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, + [4]={["y"]=894464,["x"]=-317031.71428571,}, + [5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, + }, + }, + }, + [AIRBASE.Caucasus.Sukhumi_Babushara] = { + PointsRunways = { + [1] = { + [1]={["y"]=562684,["x"]=-219779.71428571,}, + [2]={["y"]=562717.71428571,["x"]=-219718,}, + [3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, + [4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, + [5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, + }, + }, + }, + [AIRBASE.Caucasus.Tbilisi_Lochini] = { + PointsRunways = { + [1] = { + [1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, + [2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, + [3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, + [4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, + [5]={["y"]=895261.71428572,["x"]=-314656,}, + }, + [2] = { + [1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, + [2]={["y"]=897639.71428572,["x"]=-316148,}, + [3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, + [4]={["y"]=895650,["x"]=-314660,}, + [5]={["y"]=895606,["x"]=-314724.85714286,} + }, + }, + }, + [AIRBASE.Caucasus.Vaziani] = { + PointsRunways = { + [1] = { + [1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, + [2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, + [3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, + [4]={["y"]=902294.57142857,["x"]=-318146,}, + [5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, + }, + }, + }, + }, +} + +--- Creates a new ATC_GROUND_CAUCASUS object. +-- @param #ATC_GROUND_CAUCASUS self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.Caucasus enumerator). +-- @return #ATC_GROUND_CAUCASUS self +function ATC_GROUND_CAUCASUS:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, 0.05 ) + + self:SetKickSpeedKmph( 50 ) + self:SetMaximumKickSpeedKmph( 150 ) + + -- -- AnapaVityazevo + -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) + -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) + -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Batumi + -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) + -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) + -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Beslan + -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) + -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) + -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Gelendzhik + -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) + -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) + -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Gudauta + -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) + -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) + -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Kobuleti + -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) + -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) + -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- KrasnodarCenter + -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) + -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) + -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- KrasnodarPashkovsky + -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) + -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Krymsk + -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) + -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) + -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Kutaisi + -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) + -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) + -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- MaykopKhanskaya + -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) + -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) + -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- MineralnyeVody + -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) + -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) + -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Mozdok + -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) + -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) + -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Nalchik + -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) + -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) + -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Novorossiysk + -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) + -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) + -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- SenakiKolkhi + -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) + -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) + -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- SochiAdler + -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) + -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) + -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) + -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Soganlug + -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) + -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) + -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- SukhumiBabushara + -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) + -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) + -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- TbilisiLochini + -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) + -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) + -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) + -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + -- -- Vaziani + -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) + -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) + -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + -- + -- + -- + + + -- Template + -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) + -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() + -- + -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) + -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + + return self +end + + + + +--- @type ATC_GROUND_NEVADA +-- @extends #ATC_GROUND + + +--- # ATC\_GROUND\_NEVADA, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_NEVADA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Nevada is **50 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way. +-- +-- The ATC\_GROUND\_NEVADA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Nevada region. +-- Use the @{Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored. +-- +-- * `AIRBASE.Nevada.Beatty_Airport` +-- * `AIRBASE.Nevada.Boulder_City_Airport` +-- * `AIRBASE.Nevada.Creech_AFB` +-- * `AIRBASE.Nevada.Echo_Bay` +-- * `AIRBASE.Nevada.Groom_Lake_AFB` +-- * `AIRBASE.Nevada.Henderson_Executive_Airport` +-- * `AIRBASE.Nevada.Jean_Airport` +-- * `AIRBASE.Nevada.Laughlin_Airport` +-- * `AIRBASE.Nevada.Lincoln_County` +-- * `AIRBASE.Nevada.McCarran_International_Airport` +-- * `AIRBASE.Nevada.Mellan_Airstrip` +-- * `AIRBASE.Nevada.Mesquite` +-- * `AIRBASE.Nevada.Mina_Airport_3Q0` +-- * `AIRBASE.Nevada.Nellis_AFB` +-- * `AIRBASE.Nevada.North_Las_Vegas` +-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip` +-- * `AIRBASE.Nevada.Tonopah_Airport` +-- * `AIRBASE.Nevada.Tonopah_Test_Range_Airfield` +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC_GROUND_NEVADA Constructor +-- +-- Creates a new ATC_GROUND_NEVADA object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_NEVADA object. +-- +-- -- Monitor all the airbases. +-- ATC_Ground = ATC_GROUND_NEVADA:New() +-- +-- +-- -- Monitor specific airbases. +-- ATC_Ground = ATC_GROUND_NEVADA:New( +-- { AIRBASE.Nevada.Laughlin_Airport, +-- AIRBASE.Nevada.Mellan_Airstrip, +-- AIRBASE.Nevada.Lincoln_County, +-- AIRBASE.Nevada.North_Las_Vegas, +-- AIRBASE.Nevada.McCarran_International_Airport +-- } +-- ) +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +-- +-- @field #ATC_GROUND_NEVADA +ATC_GROUND_NEVADA = { + ClassName = "ATC_GROUND_NEVADA", + Airbases = { + + [AIRBASE.Nevada.Beatty_Airport] = { + PointsRunways = { + [1] = { + [1]={["y"]=-174950.05857143,["x"]=-329679.65,}, + [2]={["y"]=-174946.53828571,["x"]=-331394.03885715,}, + [3]={["y"]=-174967.10971429,["x"]=-331394.32457143,}, + [4]={["y"]=-174971.01828571,["x"]=-329682.59171429,}, + }, + }, + }, + [AIRBASE.Nevada.Boulder_City_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-1317.841714286,["x"]=-429014.92857142,}, + [2] = {["y"]=-951.26228571458,["x"]=-430310.21142856,}, + [3] = {["y"]=-978.11942857172,["x"]=-430317.06857142,}, + [4] = {["y"]=-1347.5088571432,["x"]=-429023.98485713,}, + }, + [2] = { + [1] = {["y"]=-1879.955714286,["x"]=-429783.83742856,}, + [2] = {["y"]=-256.25257142886,["x"]=-430023.63542856,}, + [3] = {["y"]=-260.25257142886,["x"]=-430048.77828571,}, + [4] = {["y"]=-1883.955714286,["x"]=-429807.83742856,}, + }, + }, + }, + [AIRBASE.Nevada.Creech_AFB] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-74234.729142857,["x"]=-360501.80857143,}, + [2] = {["y"]=-77606.122285714,["x"]=-360417.86542857,}, + [3] = {["y"]=-77608.578,["x"]=-360486.13428571,}, + [4] = {["y"]=-74237.930571428,["x"]=-360586.25628571,}, + }, + [2] = { + [1] = {["y"]=-75807.571428572,["x"]=-359073.42857142,}, + [2] = {["y"]=-74770.142857144,["x"]=-360581.71428571,}, + [3] = {["y"]=-74641.285714287,["x"]=-360585.42857142,}, + [4] = {["y"]=-75734.142857144,["x"]=-359023.14285714,}, + }, + }, + }, + [AIRBASE.Nevada.Echo_Bay] = { + PointsRunways = { + [1] = { + [1] = {["y"]=33182.919428572,["x"]=-388698.21657142,}, + [2] = {["y"]=34202.543142857,["x"]=-388469.55485714,}, + [3] = {["y"]=34207.686,["x"]=-388488.69771428,}, + [4] = {["y"]=33185.422285715,["x"]=-388717.82228571,}, + }, + }, + }, + [AIRBASE.Nevada.Groom_Lake_AFB] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-85971.465428571,["x"]=-290567.77,}, + [2] = {["y"]=-87691.155428571,["x"]=-286637.75428571,}, + [3] = {["y"]=-87756.714285715,["x"]=-286663.99999999,}, + [4] = {["y"]=-86035.940285714,["x"]=-290598.81314286,}, + }, + [2] = { + [1] = {["y"]=-86741.547142857,["x"]=-290353.31971428,}, + [2] = {["y"]=-89672.714285714,["x"]=-283546.57142855,}, + [3] = {["y"]=-89772.142857143,["x"]=-283587.71428569,}, + [4] = {["y"]=-86799.623714285,["x"]=-290374.16771428,}, + }, + }, + }, + [AIRBASE.Nevada.Henderson_Executive_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-25837.500571429,["x"]=-426404.25257142,}, + [2] = {["y"]=-25843.509428571,["x"]=-428752.67942856,}, + [3] = {["y"]=-25902.343714286,["x"]=-428749.96399999,}, + [4] = {["y"]=-25934.667142857,["x"]=-426411.45657142,}, + }, + [2] = { + [1] = {["y"]=-25650.296285714,["x"]=-426510.17971428,}, + [2] = {["y"]=-25632.443428571,["x"]=-428297.11428571,}, + [3] = {["y"]=-25686.690285714,["x"]=-428299.37457142,}, + [4] = {["y"]=-25708.296285714,["x"]=-426515.15114285,}, + }, + }, + }, + [AIRBASE.Nevada.Jean_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-42549.187142857,["x"]=-449663.23257143,}, + [2] = {["y"]=-43367.466285714,["x"]=-451044.77657143,}, + [3] = {["y"]=-43395.180571429,["x"]=-451028.20514286,}, + [4] = {["y"]=-42579.893142857,["x"]=-449648.18371428,}, + }, + [2] = { + [1] = {["y"]=-42588.359428572,["x"]=-449900.14342857,}, + [2] = {["y"]=-43349.698285714,["x"]=-451185.46857143,}, + [3] = {["y"]=-43369.624571429,["x"]=-451173.49342857,}, + [4] = {["y"]=-42609.216571429,["x"]=-449891.28628571,}, + }, + }, + }, + [AIRBASE.Nevada.Laughlin_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=28231.600857143,["x"]=-515555.94114286,}, + [2] = {["y"]=28453.728285714,["x"]=-518170.78885714,}, + [3] = {["y"]=28370.788285714,["x"]=-518176.25742857,}, + [4] = {["y"]=28138.022857143,["x"]=-515573.07514286,}, + }, + [2] = { + [1] = {["y"]=28231.600857143,["x"]=-515555.94114286,}, + [2] = {["y"]=28453.728285714,["x"]=-518170.78885714,}, + [3] = {["y"]=28370.788285714,["x"]=-518176.25742857,}, + [4] = {["y"]=28138.022857143,["x"]=-515573.07514286,}, + }, + }, + }, + [AIRBASE.Nevada.Lincoln_County] = { + PointsRunways = { + [1] = { + [1]={["y"]=33222.34171429,["x"]=-223959.40171429,}, + [2]={["y"]=33200.040000004,["x"]=-225369.36828572,}, + [3]={["y"]=33177.634571428,["x"]=-225369.21485715,}, + [4]={["y"]=33201.198857147,["x"]=-223960.54457143,}, + }, + }, + }, + [AIRBASE.Nevada.McCarran_International_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-29406.035714286,["x"]=-416102.48199999,}, + [2] = {["y"]=-24680.714285715,["x"]=-416003.14285713,}, + [3] = {["y"]=-24681.857142858,["x"]=-415926.57142856,}, + [4] = {["y"]=-29408.42857143,["x"]=-416016.57142856,}, + }, + [2] = { + [1] = {["y"]=-28567.221714286,["x"]=-416378.61799999,}, + [2] = {["y"]=-25109.912285714,["x"]=-416309.92914285,}, + [3] = {["y"]=-25112.508,["x"]=-416240.78714285,}, + [4] = {["y"]=-28576.247428571,["x"]=-416308.49514285,}, + }, + [3] = { + [1] = {["y"]=-29255.953142857,["x"]=-416307.10657142,}, + [2] = {["y"]=-28005.571428572,["x"]=-413449.7142857,}, + [3] = {["y"]=-28068.714285715,["x"]=-413422.85714284,}, + [4] = {["y"]=-29331.000000001,["x"]=-416275.7142857,}, + }, + [4] = { + [1] = {["y"]=-28994.901714286,["x"]=-416423.0522857,}, + [2] = {["y"]=-27697.571428572,["x"]=-413464.57142856,}, + [3] = {["y"]=-27767.857142858,["x"]=-413434.28571427,}, + [4] = {["y"]=-29073.000000001,["x"]=-416386.85714284,}, + }, + }, + }, + [AIRBASE.Nevada.Mesquite] = { + PointsRunways = { + [1] = { + [1] = {["y"]=68188.340285714,["x"]=-330302.54742857,}, + [2] = {["y"]=68911.303428571,["x"]=-328920.76571429,}, + [3] = {["y"]=68936.927142857,["x"]=-328933.888,}, + [4] = {["y"]=68212.460285714,["x"]=-330317.19171429,}, + }, + }, + }, + [AIRBASE.Nevada.Mina_Airport_3Q0] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-290054.57371429,["x"]=-160930.02228572,}, + [2] = {["y"]=-289469.77457143,["x"]=-162048.73571429,}, + [3] = {["y"]=-289520.06028572,["x"]=-162074.73571429,}, + [4] = {["y"]=-290104.69085714,["x"]=-160956.19457143,}, + }, + }, + }, + [AIRBASE.Nevada.Nellis_AFB] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-18614.218571428,["x"]=-399437.91085714,}, + [2] = {["y"]=-16217.857142857,["x"]=-396596.85714286,}, + [3] = {["y"]=-16300.142857143,["x"]=-396530,}, + [4] = {["y"]=-18692.543428571,["x"]=-399381.31114286,}, + }, + [2] = { + [1] = {["y"]=-18388.948857143,["x"]=-399630.51828571,}, + [2] = {["y"]=-16011,["x"]=-396806.85714286,}, + [3] = {["y"]=-16074.714285714,["x"]=-396751.71428572,}, + [4] = {["y"]=-18451.571428572,["x"]=-399580.85714285,}, + }, + }, + }, + [AIRBASE.Nevada.Pahute_Mesa_Airstrip] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-132690.40942857,["x"]=-302733.53085714,}, + [2] = {["y"]=-133112.43228571,["x"]=-304499.70742857,}, + [3] = {["y"]=-133179.91685714,["x"]=-304485.544,}, + [4] = {["y"]=-132759.988,["x"]=-302723.326,}, + }, + }, + }, + [AIRBASE.Nevada.Tonopah_Test_Range_Airfield] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-175389.162,["x"]=-224778.07685715,}, + [2] = {["y"]=-173942.15485714,["x"]=-228210.27571429,}, + [3] = {["y"]=-174001.77085714,["x"]=-228233.60371429,}, + [4] = {["y"]=-175452.38685714,["x"]=-224806.84200001,}, + }, + }, + }, + [AIRBASE.Nevada.Tonopah_Airport] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-202128.25228571,["x"]=-196701.34314286,}, + [2] = {["y"]=-201562.40828571,["x"]=-198814.99714286,}, + [3] = {["y"]=-201591.44828571,["x"]=-198820.93714286,}, + [4] = {["y"]=-202156.06828571,["x"]=-196707.68714286,}, + }, + [2] = { + [1] = {["y"]=-202084.57171428,["x"]=-196722.02228572,}, + [2] = {["y"]=-200592.75485714,["x"]=-197768.05571429,}, + [3] = {["y"]=-200605.37285714,["x"]=-197783.49228572,}, + [4] = {["y"]=-202097.14314285,["x"]=-196739.16514286,}, + }, + }, + }, + [AIRBASE.Nevada.North_Las_Vegas] = { + PointsRunways = { + [1] = { + [1] = {["y"]=-32599.017714286,["x"]=-400913.26485714,}, + [2] = {["y"]=-30881.068857143,["x"]=-400837.94628571,}, + [3] = {["y"]=-30879.354571428,["x"]=-400873.08914285,}, + [4] = {["y"]=-32595.966285714,["x"]=-400947.13571428,}, + }, + [2] = { + [1] = {["y"]=-32499.448571428,["x"]=-400690.99514285,}, + [2] = {["y"]=-31247.514857143,["x"]=-401868.95571428,}, + [3] = {["y"]=-31271.802857143,["x"]=-401894.97857142,}, + [4] = {["y"]=-32520.02,["x"]=-400716.99514285,}, + }, + [3] = { + [1] = {["y"]=-31865.254857143,["x"]=-400999.74057143,}, + [2] = {["y"]=-30893.604,["x"]=-401908.85742857,}, + [3] = {["y"]=-30915.578857143,["x"]=-401936.03685714,}, + [4] = {["y"]=-31884.969142858,["x"]=-401020.59771429,}, + }, + }, + }, + }, +} + +--- Creates a new ATC_GROUND_NEVADA object. +-- @param #ATC_GROUND_NEVADA self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.Nevada enumerator). +-- @return #ATC_GROUND_NEVADA self +function ATC_GROUND_NEVADA:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, 0.05 ) + + self:SetKickSpeedKmph( 50 ) + self:SetMaximumKickSpeedKmph( 150 ) + + -- These lines here are for the demonstration mission. + -- They create in the dcs.log the coordinates of the runway polygons, that are then + -- taken by the moose designer from the dcs.log and reworked to define the + -- Airbases structure, which is part of the class. + -- When new airbases are added or airbases are changed on the map, + -- the MOOSE designer willde-comment this section and apply the changes in the demo + -- mission, and do a re-run to create a new dcs.log, and then add the changed coordinates + -- in the Airbases structure. + -- So, this needs to stay commented normally once a map has been finished. + + --[[ + + -- Beatty + do + local VillagePrefix = "Beatty" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Boulder + do + local VillagePrefix = "Boulder" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Creech + do + local VillagePrefix = "Creech" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Echo + do + local VillagePrefix = "Echo" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Groom Lake + do + local VillagePrefix = "GroomLake" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Henderson + do + local VillagePrefix = "Henderson" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Jean + do + local VillagePrefix = "Jean" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Laughlin + do + local VillagePrefix = "Laughlin" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Lincoln + do + local VillagePrefix = "Lincoln" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- McCarran + do + local VillagePrefix = "McCarran" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway3 = GROUP:FindByName( VillagePrefix .. " 3" ) + local Zone3 = ZONE_POLYGON:New( VillagePrefix .. " 3", Runway3 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway4 = GROUP:FindByName( VillagePrefix .. " 4" ) + local Zone4 = ZONE_POLYGON:New( VillagePrefix .. " 4", Runway4 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Mesquite + do + local VillagePrefix = "Mesquite" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Mina + do + local VillagePrefix = "Mina" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Nellis + do + local VillagePrefix = "Nellis" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Pahute + do + local VillagePrefix = "Pahute" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- TonopahTR + do + local VillagePrefix = "TonopahTR" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Tonopah + do + local VillagePrefix = "Tonopah" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + -- Vegas + do + local VillagePrefix = "Vegas" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway3 = GROUP:FindByName( VillagePrefix .. " 3" ) + local Zone3 = ZONE_POLYGON:New( VillagePrefix .. " 3", Runway3 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + --]] + + return self +end + +--- @type ATC_GROUND_NORMANDY +-- @extends #ATC_GROUND + + +--- # ATC\_GROUND\_NORMANDY, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_NORMANDY class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Caucasus is **40 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **100 km/h** on the taxi way. +-- +-- The ATC\_GROUND\_NORMANDY class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Normandy region. +-- Use the @{Airbase#AIRBASE.Normandy} enumeration to select the airbases to be monitored. +-- +-- * `AIRBASE.Normandy.Azeville` +-- * `AIRBASE.Normandy.Bazenville` +-- * `AIRBASE.Normandy.Beny_sur_Mer` +-- * `AIRBASE.Normandy.Beuzeville` +-- * `AIRBASE.Normandy.Biniville` +-- * `AIRBASE.Normandy.Brucheville` +-- * `AIRBASE.Normandy.Cardonville` +-- * `AIRBASE.Normandy.Carpiquet` +-- * `AIRBASE.Normandy.Chailey` +-- * `AIRBASE.Normandy.Chippelle` +-- * `AIRBASE.Normandy.Cretteville` +-- * `AIRBASE.Normandy.Cricqueville_en_Bessin` +-- * `AIRBASE.Normandy.Deux_Jumeaux` +-- * `AIRBASE.Normandy.Evreux` +-- * `AIRBASE.Normandy.Ford` +-- * `AIRBASE.Normandy.Funtington` +-- * `AIRBASE.Normandy.Lantheuil` +-- * `AIRBASE.Normandy.Le_Molay` +-- * `AIRBASE.Normandy.Lessay` +-- * `AIRBASE.Normandy.Lignerolles` +-- * `AIRBASE.Normandy.Longues_sur_Mer` +-- * `AIRBASE.Normandy.Maupertus` +-- * `AIRBASE.Normandy.Meautis` +-- * `AIRBASE.Normandy.Needs_Oar_Point` +-- * `AIRBASE.Normandy.Picauville` +-- * `AIRBASE.Normandy.Rucqueville` +-- * `AIRBASE.Normandy.Saint_Pierre_du_Mont` +-- * `AIRBASE.Normandy.Sainte_Croix_sur_Mer` +-- * `AIRBASE.Normandy.Sainte_Laurent_sur_Mer` +-- * `AIRBASE.Normandy.Sommervieu` +-- * `AIRBASE.Normandy.Tangmere` +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC_GROUND_NORMANDY Constructor +-- +-- Creates a new ATC_GROUND_NORMANDY object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_NORMANDY object. +-- +-- -- Monitor for these clients the airbases. +-- AirbasePoliceCaucasus = ATC_GROUND_NORMANDY:New() +-- +-- ATC_Ground = ATC_GROUND_NORMANDY:New( +-- { AIRBASE.Normandy.Chippelle, +-- AIRBASE.Normandy.Beuzeville +-- } +-- ) +-- +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +-- @field #ATC_GROUND_NORMANDY +ATC_GROUND_NORMANDY = { + ClassName = "ATC_GROUND_NORMANDY", + Airbases = { + [AIRBASE.Normandy.Azeville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-74194.387714285,["x"]=-2691.1399999998,}, + [2]={["y"]=-73160.282571428,["x"]=-2310.0274285712,}, + [3]={["y"]=-73141.711142857,["x"]=-2357.7417142855,}, + [4]={["y"]=-74176.959142857,["x"]=-2741.997142857,}, + }, + }, + }, + [AIRBASE.Normandy.Bazenville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-19246.209999999,["x"]=-21246.748,}, + [2]={["y"]=-17883.70142857,["x"]=-20219.009714285,}, + [3]={["y"]=-17855.415714285,["x"]=-20256.438285714,}, + [4]={["y"]=-19217.791999999,["x"]=-21283.597714285,}, + }, + }, + }, + [AIRBASE.Normandy.Beny_sur_Mer] = { + PointsRunways = { + [1] = { + [1]={["y"]=-8592.7442857133,["x"]=-20386.15542857,}, + [2]={["y"]=-8404.4931428561,["x"]=-21744.113142856,}, + [3]={["y"]=-8267.9917142847,["x"]=-21724.97742857,}, + [4]={["y"]=-8451.0482857133,["x"]=-20368.87542857,}, + }, + }, + }, + [AIRBASE.Normandy.Beuzeville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-71552.573428571,["x"]=-8744.3688571427,}, + [2]={["y"]=-72577.765714285,["x"]=-9638.5682857141,}, + [3]={["y"]=-72609.304285714,["x"]=-9601.2954285712,}, + [4]={["y"]=-71585.849428571,["x"]=-8709.9648571426,}, + }, + }, + }, + [AIRBASE.Normandy.Biniville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-84757.320285714,["x"]=-7377.1354285713,}, + [2]={["y"]=-84271.482,["x"]=-7956.4859999999,}, + [3]={["y"]=-84299.482,["x"]=-7981.6288571427,}, + [4]={["y"]=-84784.969714286,["x"]=-7402.0588571427,}, + }, + }, + }, + [AIRBASE.Normandy.Brucheville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-65546.792857142,["x"]=-14615.640857143,}, + [2]={["y"]=-66914.692,["x"]=-15232.713714285,}, + [3]={["y"]=-66896.527714285,["x"]=-15271.948571428,}, + [4]={["y"]=-65528.393714285,["x"]=-14657.995714286,}, + }, + }, + }, + [AIRBASE.Normandy.Cardonville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-54280.445428571,["x"]=-15843.749142857,}, + [2]={["y"]=-53646.998571428,["x"]=-17143.012285714,}, + [3]={["y"]=-53683.93,["x"]=-17161.317428571,}, + [4]={["y"]=-54323.354571428,["x"]=-15855.004,}, + }, + }, + }, + [AIRBASE.Normandy.Carpiquet] = { + PointsRunways = { + [1] = { + [1]={["y"]=-10751.325714285,["x"]=-34229.494,}, + [2]={["y"]=-9283.5279999993,["x"]=-35192.352857142,}, + [3]={["y"]=-9325.2005714274,["x"]=-35260.967714285,}, + [4]={["y"]=-10794.90942857,["x"]=-34287.041428571,}, + }, + }, + }, + [AIRBASE.Normandy.Chailey] = { + PointsRunways = { + [1] = { + [1]={["y"]=12895.585714292,["x"]=164683.05657144,}, + [2]={["y"]=11410.727142863,["x"]=163606.54485715,}, + [3]={["y"]=11363.012857149,["x"]=163671.97342858,}, + [4]={["y"]=12797.537142863,["x"]=164711.01857144,}, + [5]={["y"]=12862.902857149,["x"]=164726.99685715,}, + }, + [2] = { + [1]={["y"]=11805.316000006,["x"]=164502.90971429,}, + [2]={["y"]=11997.280857149,["x"]=163032.65542858,}, + [3]={["y"]=11918.640857149,["x"]=163023.04657144,}, + [4]={["y"]=11726.973428578,["x"]=164489.94257143,}, + }, + }, + }, + [AIRBASE.Normandy.Chippelle] = { + PointsRunways = { + [1] = { + [1]={["y"]=-48540.313999999,["x"]=-28884.795999999,}, + [2]={["y"]=-47251.820285713,["x"]=-28140.128571427,}, + [3]={["y"]=-47274.551714285,["x"]=-28103.758285713,}, + [4]={["y"]=-48555.657714285,["x"]=-28839.90142857,}, + }, + }, + }, + [AIRBASE.Normandy.Cretteville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-78351.723142857,["x"]=-18177.725428571,}, + [2]={["y"]=-77220.322285714,["x"]=-19125.687714286,}, + [3]={["y"]=-77247.899428571,["x"]=-19158.49,}, + [4]={["y"]=-78380.008857143,["x"]=-18208.011142857,}, + }, + }, + }, + [AIRBASE.Normandy.Cricqueville_en_Bessin] = { + PointsRunways = { + [1] = { + [1]={["y"]=-50875.034571428,["x"]=-14322.404571428,}, + [2]={["y"]=-50681.148571428,["x"]=-15825.258,}, + [3]={["y"]=-50717.434285713,["x"]=-15829.829428571,}, + [4]={["y"]=-50910.569428571,["x"]=-14327.562857142,}, + }, + }, + }, + [AIRBASE.Normandy.Deux_Jumeaux] = { + PointsRunways = { + [1] = { + [1]={["y"]=-49575.410857142,["x"]=-16575.161142857,}, + [2]={["y"]=-48149.077999999,["x"]=-16952.193428571,}, + [3]={["y"]=-48159.935142856,["x"]=-16996.764857142,}, + [4]={["y"]=-49584.839428571,["x"]=-16617.732571428,}, + }, + }, + }, + [AIRBASE.Normandy.Evreux] = { + PointsRunways = { + [1] = { + [1]={["y"]=112906.84828572,["x"]=-45585.824857142,}, + [2]={["y"]=112050.38228572,["x"]=-46811.871999999,}, + [3]={["y"]=111980.05371429,["x"]=-46762.173142856,}, + [4]={["y"]=112833.54542857,["x"]=-45540.010571428,}, + }, + [2] = { + [1]={["y"]=112046.02085714,["x"]=-45091.056571428,}, + [2]={["y"]=112488.668,["x"]=-46623.617999999,}, + [3]={["y"]=112405.66914286,["x"]=-46647.419142856,}, + [4]={["y"]=111966.03657143,["x"]=-45112.604285713,}, + }, + }, + }, + [AIRBASE.Normandy.Ford] = { + PointsRunways = { + [1] = { + [1]={["y"]=-26506.13971428,["x"]=147514.39971429,}, + [2]={["y"]=-25012.977428565,["x"]=147566.14485715,}, + [3]={["y"]=-25009.851428565,["x"]=147482.63600001,}, + [4]={["y"]=-26503.693999994,["x"]=147427.33228572,}, + }, + [2] = { + [1]={["y"]=-25169.701999994,["x"]=148421.09257143,}, + [2]={["y"]=-26092.421999994,["x"]=147190.89628572,}, + [3]={["y"]=-26158.136285708,["x"]=147240.89628572,}, + [4]={["y"]=-25252.357999994,["x"]=148448.64457143,}, + }, + }, + }, + [AIRBASE.Normandy.Funtington] = { + PointsRunways = { + [1] = { + [1]={["y"]=-44698.388571423,["x"]=152952.17257143,}, + [2]={["y"]=-46452.993142851,["x"]=152388.77885714,}, + [3]={["y"]=-46476.361142851,["x"]=152470.05885714,}, + [4]={["y"]=-44787.256571423,["x"]=153009.52,}, + [5]={["y"]=-44715.581428566,["x"]=153002.08714286,}, + }, + [2] = { + [1]={["y"]=-45792.665999994,["x"]=153123.894,}, + [2]={["y"]=-46068.084857137,["x"]=151665.98342857,}, + [3]={["y"]=-46148.632285708,["x"]=151681.58685714,}, + [4]={["y"]=-45871.25971428,["x"]=153136.82714286,}, + }, + }, + }, + [AIRBASE.Normandy.Lantheuil] = { + PointsRunways = { + [1] = { + [1]={["y"]=-17158.84542857,["x"]=-24602.999428571,}, + [2]={["y"]=-15978.59342857,["x"]=-23922.978571428,}, + [3]={["y"]=-15932.021999999,["x"]=-24004.121428571,}, + [4]={["y"]=-17090.734857142,["x"]=-24673.248,}, + }, + }, + }, + [AIRBASE.Normandy.Lessay] = { + PointsRunways = { + [1] = { + [1]={["y"]=-87667.304571429,["x"]=-33220.165714286,}, + [2]={["y"]=-86146.607714286,["x"]=-34248.483142857,}, + [3]={["y"]=-86191.538285714,["x"]=-34316.991142857,}, + [4]={["y"]=-87712.212,["x"]=-33291.774857143,}, + }, + [2] = { + [1]={["y"]=-87125.123142857,["x"]=-34183.682571429,}, + [2]={["y"]=-85803.278285715,["x"]=-33498.428857143,}, + [3]={["y"]=-85768.408285715,["x"]=-33570.13,}, + [4]={["y"]=-87087.688571429,["x"]=-34258.272285715,}, + }, + }, + }, + [AIRBASE.Normandy.Lignerolles] = { + PointsRunways = { + [1] = { + [1]={["y"]=-35279.611714285,["x"]=-35232.026857142,}, + [2]={["y"]=-33804.948857142,["x"]=-35770.713999999,}, + [3]={["y"]=-33789.876285713,["x"]=-35726.655714284,}, + [4]={["y"]=-35263.548285713,["x"]=-35192.75542857,}, + }, + }, + }, + [AIRBASE.Normandy.Longues_sur_Mer] = { + PointsRunways = { + [1] = { + [1]={["y"]=-29444.070285713,["x"]=-16334.105428571,}, + [2]={["y"]=-28265.52942857,["x"]=-17011.557999999,}, + [3]={["y"]=-28344.74742857,["x"]=-17143.587999999,}, + [4]={["y"]=-29529.616285713,["x"]=-16477.766571428,}, + }, + }, + }, + [AIRBASE.Normandy.Maupertus] = { + PointsRunways = { + [1] = { + [1]={["y"]=-85605.340857143,["x"]=16175.267714286,}, + [2]={["y"]=-84132.567142857,["x"]=15895.905714286,}, + [3]={["y"]=-84139.995142857,["x"]=15847.623714286,}, + [4]={["y"]=-85613.626571429,["x"]=16132.410571429,}, + }, + }, + }, + [AIRBASE.Normandy.Meautis] = { + PointsRunways = { + [1] = { + [1]={["y"]=-72642.527714286,["x"]=-24593.622285714,}, + [2]={["y"]=-71298.672571429,["x"]=-24352.651142857,}, + [3]={["y"]=-71290.101142857,["x"]=-24398.365428571,}, + [4]={["y"]=-72631.715714286,["x"]=-24639.966857143,}, + }, + }, + }, + [AIRBASE.Normandy.Le_Molay] = { + PointsRunways = { + [1] = { + [1]={["y"]=-41876.526857142,["x"]=-26701.052285713,}, + [2]={["y"]=-40979.545714285,["x"]=-25675.045999999,}, + [3]={["y"]=-41017.687428571,["x"]=-25644.272571427,}, + [4]={["y"]=-41913.638285713,["x"]=-26665.137999999,}, + }, + }, + }, + [AIRBASE.Normandy.Needs_Oar_Point] = { + PointsRunways = { + [1] = { + [1]={["y"]=-83882.441142851,["x"]=141429.83314286,}, + [2]={["y"]=-85138.159428566,["x"]=140187.52828572,}, + [3]={["y"]=-85208.323428566,["x"]=140161.04371429,}, + [4]={["y"]=-85245.751999994,["x"]=140201.61514286,}, + [5]={["y"]=-83939.966571423,["x"]=141485.22085714,}, + }, + [2] = { + [1]={["y"]=-84528.76571428,["x"]=141988.01428572,}, + [2]={["y"]=-84116.98971428,["x"]=140565.78685714,}, + [3]={["y"]=-84199.35771428,["x"]=140541.14685714,}, + [4]={["y"]=-84605.051428566,["x"]=141966.01428572,}, + }, + }, + }, + [AIRBASE.Normandy.Picauville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-80808.838571429,["x"]=-11834.554571428,}, + [2]={["y"]=-79531.574285714,["x"]=-12311.274,}, + [3]={["y"]=-79549.355428571,["x"]=-12356.928285714,}, + [4]={["y"]=-80827.815142857,["x"]=-11901.835142857,}, + }, + }, + }, + [AIRBASE.Normandy.Rucqueville] = { + PointsRunways = { + [1] = { + [1]={["y"]=-20023.988857141,["x"]=-26569.565428571,}, + [2]={["y"]=-18688.92542857,["x"]=-26571.086571428,}, + [3]={["y"]=-18688.012571427,["x"]=-26611.252285713,}, + [4]={["y"]=-20022.218857141,["x"]=-26608.505428571,}, + }, + }, + }, + [AIRBASE.Normandy.Saint_Pierre_du_Mont] = { + PointsRunways = { + [1] = { + [1]={["y"]=-48015.384571428,["x"]=-11886.631714285,}, + [2]={["y"]=-46540.412285713,["x"]=-11945.226571428,}, + [3]={["y"]=-46541.349999999,["x"]=-11991.174571428,}, + [4]={["y"]=-48016.837142856,["x"]=-11929.371142857,}, + }, + }, + }, + [AIRBASE.Normandy.Sainte_Croix_sur_Mer] = { + PointsRunways = { + [1] = { + [1]={["y"]=-15877.817999999,["x"]=-18812.579999999,}, + [2]={["y"]=-14464.377142856,["x"]=-18807.46,}, + [3]={["y"]=-14463.879714285,["x"]=-18759.706857142,}, + [4]={["y"]=-15878.229142856,["x"]=-18764.071428571,}, + }, + }, + }, + [AIRBASE.Normandy.Sainte_Laurent_sur_Mer] = { + PointsRunways = { + [1] = { + [1]={["y"]=-41676.834857142,["x"]=-14475.109428571,}, + [2]={["y"]=-40566.11142857,["x"]=-14817.319999999,}, + [3]={["y"]=-40579.543999999,["x"]=-14860.059999999,}, + [4]={["y"]=-41687.120571427,["x"]=-14509.680857142,}, + }, + }, + }, + [AIRBASE.Normandy.Sommervieu] = { + PointsRunways = { + [1] = { + [1]={["y"]=-26821.913714284,["x"]=-21390.466571427,}, + [2]={["y"]=-25465.308857142,["x"]=-21296.859999999,}, + [3]={["y"]=-25462.451714284,["x"]=-21343.717142856,}, + [4]={["y"]=-26818.002285713,["x"]=-21440.532857142,}, + }, + }, + }, + [AIRBASE.Normandy.Tangmere] = { + PointsRunways = { + [1] = { + [1]={["y"]=-34684.581142851,["x"]=150459.61657143,}, + [2]={["y"]=-33250.625428566,["x"]=149954.17,}, + [3]={["y"]=-33275.724285708,["x"]=149874.69028572,}, + [4]={["y"]=-34709.020571423,["x"]=150377.93742857,}, + }, + [2] = { + [1]={["y"]=-33103.438857137,["x"]=150812.72542857,}, + [2]={["y"]=-34410.246285708,["x"]=150009.73142857,}, + [3]={["y"]=-34453.535142851,["x"]=150082.02685714,}, + [4]={["y"]=-33176.545999994,["x"]=150870.22542857,}, + }, + }, + }, + }, +} + + +--- Creates a new ATC_GROUND_NORMANDY object. +-- @param #ATC_GROUND_NORMANDY self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.Normandy enumerator). +-- @return #ATC_GROUND_NORMANDY self +function ATC_GROUND_NORMANDY:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) -- #ATC_GROUND_NORMANDY + + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, 0.05 ) + + self:SetKickSpeedKmph( 40 ) + self:SetMaximumKickSpeedKmph( 100 ) + + -- These lines here are for the demonstration mission. + -- They create in the dcs.log the coordinates of the runway polygons, that are then + -- taken by the moose designer from the dcs.log and reworked to define the + -- Airbases structure, which is part of the class. + -- When new airbases are added or airbases are changed on the map, + -- the MOOSE designer willde-comment this section and apply the changes in the demo + -- mission, and do a re-run to create a new dcs.log, and then add the changed coordinates + -- in the Airbases structure. + -- So, this needs to stay commented normally once a map has been finished. + + --[[ + + -- Azeville + do + local VillagePrefix = "Azeville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Bazenville + do + local VillagePrefix = "Bazenville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Beny + do + local VillagePrefix = "Beny" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Beuzeville + do + local VillagePrefix = "Beuzeville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Biniville + do + local VillagePrefix = "Biniville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Brucheville + do + local VillagePrefix = "Brucheville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Cardonville + do + local VillagePrefix = "Cardonville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Carpiquet + do + local VillagePrefix = "Carpiquet" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Chailey + do + local VillagePrefix = "Chailey" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Chippelle + do + local VillagePrefix = "Chippelle" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Cretteville + do + local VillagePrefix = "Cretteville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Cricqueville + do + local VillagePrefix = "Cricqueville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Deux + do + local VillagePrefix = "Deux" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Evreux + do + local VillagePrefix = "Evreux" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Ford + do + local VillagePrefix = "Ford" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Funtington + do + local VillagePrefix = "Funtington" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Lantheuil + do + local VillagePrefix = "Lantheuil" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Lessay + do + local VillagePrefix = "Lessay" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Lignerolles + do + local VillagePrefix = "Lignerolles" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Longues + do + local VillagePrefix = "Longues" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Maupertus + do + local VillagePrefix = "Maupertus" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Meautis + do + local VillagePrefix = "Meautis" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Molay + do + local VillagePrefix = "Molay" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Oar + do + local VillagePrefix = "Oar" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Picauville + do + local VillagePrefix = "Picauville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Rucqueville + do + local VillagePrefix = "Rucqueville" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- SaintPierre + do + local VillagePrefix = "SaintPierre" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- SainteCroix + do + local VillagePrefix = "SainteCroix" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + --SainteLaurent + do + local VillagePrefix = "SainteLaurent" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Sommervieu + do + local VillagePrefix = "Sommervieu" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + + -- Tangmere + do + local VillagePrefix = "Tangmere" + local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) + local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) + local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() + end + + --]] + + return self +end + + + + + \ No newline at end of file diff --git a/Moose Development/Moose/Functional/AirbasePolice.lua b/Moose Development/Moose/Functional/AirbasePolice.lua deleted file mode 100644 index 1d628db80..000000000 --- a/Moose Development/Moose/Functional/AirbasePolice.lua +++ /dev/null @@ -1,1197 +0,0 @@ ---- **Functional** -- This module monitors airbases traffic. --- --- === --- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} --- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. --- CLIENTS should not be allowed to: --- --- * Don't taxi faster than 40 km/h. --- * Don't take-off on taxiways. --- * Avoid to hit other planes on the airbase. --- * Obey ground control orders. --- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the caucasus map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * AnapaVityazevo --- * Batumi --- * Beslan --- * Gelendzhik --- * Gudauta --- * Kobuleti --- * KrasnodarCenter --- * KrasnodarPashkovsky --- * Krymsk --- * Kutaisi --- * MaykopKhanskaya --- * MineralnyeVody --- * Mozdok --- * Nalchik --- * Novorossiysk --- * SenakiKolkhi --- * SochiAdler --- * Soganlug --- * SukhumiBabushara --- * TbilisiLochini --- * Vaziani --- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the NEVADA map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * Nellis --- * McCarran --- * Creech --- * Groom Lake --- --- ### Contributions: Dutch Baron - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module AirbasePolice - - - - - ---- @type AIRBASEPOLICE_BASE --- @field Core.Set#SET_CLIENT SetClient --- @extends Core.Base#BASE - -AIRBASEPOLICE_BASE = { - ClassName = "AIRBASEPOLICE_BASE", - SetClient = nil, - Airbases = nil, - AirbaseNames = nil, -} - - ---- Creates a new AIRBASEPOLICE_BASE object. --- @param #AIRBASEPOLICE_BASE self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @param Airbases A table of Airbase Names. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - self:E( { self.ClassName, SetClient, Airbases } ) - - self.SetClient = SetClient - self.Airbases = Airbases - - for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() - end - end - --- -- Template --- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client - function( Client ) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0) - Client:SetState( self, "Taxi", false ) - end - ) - - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, {}, 0, 2, 0.05 ) - - return self -end - ---- @type AIRBASEPOLICE_BASE.AirbaseNames --- @list <#string> - ---- Monitor a table of airbase names. --- @param #AIRBASEPOLICE_BASE self --- @param #AIRBASEPOLICE_BASE.AirbaseNames AirbaseNames A list of AirbaseNames to monitor. If this parameters is nil, then all airbases will be monitored. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:Monitor( AirbaseNames ) - - if AirbaseNames then - if type( AirbaseNames ) == "table" then - self.AirbaseNames = AirbaseNames - else - self.AirbaseNames = { AirbaseNames } - end - end -end - ---- @param #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:_AirbaseMonitor() - - for AirbaseID, Airbase in pairs( self.Airbases ) do - - if not self.AirbaseNames or self.AirbaseNames[AirbaseID] then - - self:E( AirbaseID ) - - self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - - --- @param Wrapper.Client#CLIENT Client - function( Client ) - - self:E( Client.UnitName ) - if Client:IsAlive() then - local NotInRunwayZone = true - for ZoneRunwayID, ZoneRunway in pairs( Airbase.ZoneRunways ) do - NotInRunwayZone = ( Client:IsNotInZone( ZoneRunway ) == true ) and NotInRunwayZone or false - end - - if NotInRunwayZone then - local Taxi = self:GetState( self, "Taxi" ) - self:E( Taxi ) - if Taxi == false then - Client:Message( "Welcome at " .. AirbaseID .. ". The maximum taxiing speed is " .. Airbase.MaximumSpeed " km/h.", 20, "ATC" ) - self:SetState( self, "Taxi", true ) - end - - -- TODO: GetVelocityKMH function usage - local VelocityVec3 = Client:GetVelocity() - 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. - -- MESSAGE:New( "Velocity = " .. Velocity, 1 ):ToAll() - local IsAboveRunway = Client:IsAboveRunway() - local IsOnGround = Client:InAir() == false - self:T( IsAboveRunway, IsOnGround ) - - if IsAboveRunway and IsOnGround then - - if Velocity > Airbase.MaximumSpeed then - local IsSpeeding = Client:GetState( self, "Speeding" ) - - if IsSpeeding == true then - local SpeedingWarnings = Client:GetState( self, "Warnings" ) - self:T( SpeedingWarnings ) - - if SpeedingWarnings <= 3 then - Client:Message( "You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Warning " .. SpeedingWarnings .. " / 3" ) - Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) - else - MESSAGE:New( "Player " .. Client:GetPlayerName() .. " has been removed from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() - Client:Destroy() - trigger.action.setUserFlag( "AIRCRAFT_"..Client:GetID(), 100) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - - else - Client:Message( "You are speeding on the taxiway, slow down now! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Attention! " ) - Client:SetState( self, "Speeding", true ) - Client:SetState( self, "Warnings", 1 ) - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - local Taxi = self:GetState( self, "Taxi" ) - if Taxi == true then - Client:Message( "You have progressed to the runway ... Await take-off clearance ...", 20, "ATC" ) - self:SetState( self, "Taxi", false ) - end - end - end - end - ) - end - end - - return true -end - - ---- @type AIRBASEPOLICE_CAUCASUS --- @field Core.Set#SET_CLIENT SetClient --- @extends #AIRBASEPOLICE_BASE - -AIRBASEPOLICE_CAUCASUS = { - ClassName = "AIRBASEPOLICE_CAUCASUS", - Airbases = { - AnapaVityazevo = { - PointsBoundary = { - [1]={["y"]=242234.85714287,["x"]=-6616.5714285726,}, - [2]={["y"]=241060.57142858,["x"]=-5585.142857144,}, - [3]={["y"]=243806.2857143,["x"]=-3962.2857142868,}, - [4]={["y"]=245240.57142858,["x"]=-4816.5714285726,}, - [5]={["y"]=244783.42857144,["x"]=-5630.8571428583,}, - [6]={["y"]=243800.57142858,["x"]=-5065.142857144,}, - [7]={["y"]=242232.00000001,["x"]=-6622.2857142868,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, - [2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, - [3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, - [4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, - [5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Batumi = { - PointsBoundary = { - [1]={["y"]=617567.14285714,["x"]=-355313.14285715,}, - [2]={["y"]=616181.42857142,["x"]=-354800.28571429,}, - [3]={["y"]=616007.14285714,["x"]=-355128.85714286,}, - [4]={["y"]=618230,["x"]=-356914.57142858,}, - [5]={["y"]=618727.14285714,["x"]=-356166,}, - [6]={["y"]=617572.85714285,["x"]=-355308.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, - [2]={["y"]=618450.57142857,["x"]=-356522,}, - [3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, - [4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, - [5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, - [6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, - [7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, - [8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, - [9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, - [10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, - [11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, - [12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, - [13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, - [14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Beslan = { - PointsBoundary = { - [1]={["y"]=842082.57142857,["x"]=-148445.14285715,}, - [2]={["y"]=845237.71428572,["x"]=-148639.71428572,}, - [3]={["y"]=845232,["x"]=-148765.42857143,}, - [4]={["y"]=844220.57142857,["x"]=-149168.28571429,}, - [5]={["y"]=843274.85714286,["x"]=-149125.42857143,}, - [6]={["y"]=842077.71428572,["x"]=-148554,}, - [7]={["y"]=842083.42857143,["x"]=-148445.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, - [2]={["y"]=845225.71428572,["x"]=-148656,}, - [3]={["y"]=845220.57142858,["x"]=-148750,}, - [4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, - [5]={["y"]=842104,["x"]=-148460.28571429,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gelendzhik = { - PointsBoundary = { - [1]={["y"]=297856.00000001,["x"]=-51151.428571429,}, - [2]={["y"]=299044.57142858,["x"]=-49720.000000001,}, - [3]={["y"]=298861.71428572,["x"]=-49580.000000001,}, - [4]={["y"]=298198.85714286,["x"]=-49842.857142858,}, - [5]={["y"]=297990.28571429,["x"]=-50151.428571429,}, - [6]={["y"]=297696.00000001,["x"]=-51054.285714286,}, - [7]={["y"]=297850.28571429,["x"]=-51160.000000001,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, - [2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, - [3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, - [4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, - [5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gudauta = { - PointsBoundary = { - [1]={["y"]=517246.57142857,["x"]=-197850.28571429,}, - [2]={["y"]=516749.42857142,["x"]=-198070.28571429,}, - [3]={["y"]=515755.14285714,["x"]=-197598.85714286,}, - [4]={["y"]=515369.42857142,["x"]=-196538.85714286,}, - [5]={["y"]=515623.71428571,["x"]=-195618.85714286,}, - [6]={["y"]=515946.57142857,["x"]=-195510.28571429,}, - [7]={["y"]=517243.71428571,["x"]=-197858.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, - [2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, - [3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, - [4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, - [5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kobuleti = { - PointsBoundary = { - [1]={["y"]=634427.71428571,["x"]=-318290.28571429,}, - [2]={["y"]=635033.42857143,["x"]=-317550.2857143,}, - [3]={["y"]=635864.85714286,["x"]=-317333.14285715,}, - [4]={["y"]=636967.71428571,["x"]=-317261.71428572,}, - [5]={["y"]=637144.85714286,["x"]=-317913.14285715,}, - [6]={["y"]=634630.57142857,["x"]=-318687.42857144,}, - [7]={["y"]=634424.85714286,["x"]=-318290.2857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, - [2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, - [3]={["y"]=636790,["x"]=-317575.71428572,}, - [4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, - [5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarCenter = { - PointsBoundary = { - [1]={["y"]=366680.28571429,["x"]=11699.142857142,}, - [2]={["y"]=366654.28571429,["x"]=11225.142857142,}, - [3]={["y"]=367497.14285715,["x"]=11082.285714285,}, - [4]={["y"]=368025.71428572,["x"]=10396.57142857,}, - [5]={["y"]=369854.28571429,["x"]=11367.999999999,}, - [6]={["y"]=369840.00000001,["x"]=11910.857142856,}, - [7]={["y"]=366682.57142858,["x"]=11697.999999999,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=369205.42857144,["x"]=11789.142857142,}, - [2]={["y"]=369209.71428572,["x"]=11714.857142856,}, - [3]={["y"]=366699.71428572,["x"]=11581.714285713,}, - [4]={["y"]=366698.28571429,["x"]=11659.142857142,}, - [5]={["y"]=369208.85714286,["x"]=11788.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarPashkovsky = { - PointsBoundary = { - [1]={["y"]=386754,["x"]=6476.5714285703,}, - [2]={["y"]=389182.57142858,["x"]=8722.2857142846,}, - [3]={["y"]=388832.57142858,["x"]=9086.5714285703,}, - [4]={["y"]=386961.14285715,["x"]=7707.9999999989,}, - [5]={["y"]=385404,["x"]=9179.4285714274,}, - [6]={["y"]=383239.71428572,["x"]=7386.5714285703,}, - [7]={["y"]=383954,["x"]=6486.5714285703,}, - [8]={["y"]=385775.42857143,["x"]=8097.9999999989,}, - [9]={["y"]=386804,["x"]=7319.4285714274,}, - [10]={["y"]=386375.42857143,["x"]=6797.9999999989,}, - [11]={["y"]=386746.85714286,["x"]=6472.2857142846,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - [2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, - [3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, - [4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, - [5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - }, - [2] = { - [1]={["y"]=386714.85714286,["x"]=6674.857142856,}, - [2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, - [3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, - [4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, - [5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Krymsk = { - PointsBoundary = { - [1]={["y"]=293338.00000001,["x"]=-7575.4285714297,}, - [2]={["y"]=295199.42857144,["x"]=-5434.0000000011,}, - [3]={["y"]=295595.14285715,["x"]=-6239.7142857154,}, - [4]={["y"]=294152.2857143,["x"]=-8325.4285714297,}, - [5]={["y"]=293345.14285715,["x"]=-7596.8571428582,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, - [2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, - [3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, - [4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, - [5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kutaisi = { - PointsBoundary = { - [1]={["y"]=682087.42857143,["x"]=-284512.85714286,}, - [2]={["y"]=685387.42857143,["x"]=-283662.85714286,}, - [3]={["y"]=685294.57142857,["x"]=-284977.14285715,}, - [4]={["y"]=682744.57142857,["x"]=-286505.71428572,}, - [5]={["y"]=682094.57142857,["x"]=-284527.14285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=682638,["x"]=-285202.28571429,}, - [2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, - [3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, - [4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, - [5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MaykopKhanskaya = { - PointsBoundary = { - [1]={["y"]=456876.28571429,["x"]=-27665.42857143,}, - [2]={["y"]=457800,["x"]=-28392.857142858,}, - [3]={["y"]=459368.57142857,["x"]=-26378.571428573,}, - [4]={["y"]=459425.71428572,["x"]=-25242.857142858,}, - [5]={["y"]=458961.42857143,["x"]=-24964.285714287,}, - [6]={["y"]=456878.57142857,["x"]=-27667.714285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, - [2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, - [3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, - [4]={["y"]=457060,["x"]=-27714.285714287,}, - [5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MineralnyeVody = { - PointsBoundary = { - [1]={["y"]=703857.14285714,["x"]=-50226.000000002,}, - [2]={["y"]=707385.71428571,["x"]=-51911.714285716,}, - [3]={["y"]=707595.71428571,["x"]=-51434.857142859,}, - [4]={["y"]=707900,["x"]=-51568.857142859,}, - [5]={["y"]=707542.85714286,["x"]=-52326.000000002,}, - [6]={["y"]=706628.57142857,["x"]=-52568.857142859,}, - [7]={["y"]=705142.85714286,["x"]=-51790.285714288,}, - [8]={["y"]=703678.57142857,["x"]=-50611.714285716,}, - [9]={["y"]=703857.42857143,["x"]=-50226.857142859,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=703904,["x"]=-50352.571428573,}, - [2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, - [3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, - [4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, - [5]={["y"]=703902,["x"]=-50352.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Mozdok = { - PointsBoundary = { - [1]={["y"]=832123.42857143,["x"]=-83608.571428573,}, - [2]={["y"]=835916.28571429,["x"]=-83144.285714288,}, - [3]={["y"]=835474.28571429,["x"]=-84170.571428573,}, - [4]={["y"]=832911.42857143,["x"]=-84470.571428573,}, - [5]={["y"]=832487.71428572,["x"]=-85565.714285716,}, - [6]={["y"]=831573.42857143,["x"]=-85351.42857143,}, - [7]={["y"]=832123.71428572,["x"]=-83610.285714288,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, - [2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, - [3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, - [4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, - [5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Nalchik = { - PointsBoundary = { - [1]={["y"]=759370,["x"]=-125502.85714286,}, - [2]={["y"]=761384.28571429,["x"]=-124177.14285714,}, - [3]={["y"]=761472.85714286,["x"]=-124325.71428572,}, - [4]={["y"]=761092.85714286,["x"]=-125048.57142857,}, - [5]={["y"]=760295.71428572,["x"]=-125685.71428572,}, - [6]={["y"]=759444.28571429,["x"]=-125734.28571429,}, - [7]={["y"]=759375.71428572,["x"]=-125511.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, - [2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, - [3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, - [4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, - [5]={["y"]=759456,["x"]=-125552.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Novorossiysk = { - PointsBoundary = { - [1]={["y"]=278677.71428573,["x"]=-41656.571428572,}, - [2]={["y"]=278446.2857143,["x"]=-41453.714285715,}, - [3]={["y"]=278989.14285716,["x"]=-40188.000000001,}, - [4]={["y"]=279717.71428573,["x"]=-39968.000000001,}, - [5]={["y"]=280020.57142859,["x"]=-40208.000000001,}, - [6]={["y"]=278674.85714287,["x"]=-41660.857142858,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, - [2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, - [3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, - [4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, - [5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SenakiKolkhi = { - PointsBoundary = { - [1]={["y"]=646036.57142857,["x"]=-281778.85714286,}, - [2]={["y"]=646045.14285714,["x"]=-281191.71428571,}, - [3]={["y"]=647032.28571429,["x"]=-280598.85714285,}, - [4]={["y"]=647669.42857143,["x"]=-281273.14285714,}, - [5]={["y"]=648323.71428571,["x"]=-281370.28571428,}, - [6]={["y"]=648520.85714286,["x"]=-281978.85714285,}, - [7]={["y"]=646039.42857143,["x"]=-281783.14285714,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=646060.85714285,["x"]=-281736,}, - [2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, - [3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, - [4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, - [5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SochiAdler = { - PointsBoundary = { - [1]={["y"]=460642.28571428,["x"]=-164861.71428571,}, - [2]={["y"]=462820.85714285,["x"]=-163368.85714286,}, - [3]={["y"]=463649.42857142,["x"]=-163340.28571429,}, - [4]={["y"]=463835.14285714,["x"]=-164040.28571429,}, - [5]={["y"]=462535.14285714,["x"]=-165654.57142857,}, - [6]={["y"]=460678,["x"]=-165247.42857143,}, - [7]={["y"]=460635.14285714,["x"]=-164876,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - [2] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Soganlug = { - PointsBoundary = { - [1]={["y"]=894530.85714286,["x"]=-316928.28571428,}, - [2]={["y"]=896422.28571428,["x"]=-318622.57142857,}, - [3]={["y"]=896090.85714286,["x"]=-318934,}, - [4]={["y"]=894019.42857143,["x"]=-317119.71428571,}, - [5]={["y"]=894533.71428571,["x"]=-316925.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=894525.71428571,["x"]=-316964,}, - [2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, - [3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, - [4]={["y"]=894464,["x"]=-317031.71428571,}, - [5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SukhumiBabushara = { - PointsBoundary = { - [1]={["y"]=562541.14285714,["x"]=-219852.28571429,}, - [2]={["y"]=562691.14285714,["x"]=-219395.14285714,}, - [3]={["y"]=564326.85714286,["x"]=-219523.71428571,}, - [4]={["y"]=566262.57142857,["x"]=-221166.57142857,}, - [5]={["y"]=566069.71428571,["x"]=-221580.85714286,}, - [6]={["y"]=562534,["x"]=-219873.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=562684,["x"]=-219779.71428571,}, - [2]={["y"]=562717.71428571,["x"]=-219718,}, - [3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, - [4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, - [5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - TbilisiLochini = { - PointsBoundary = { - [1]={["y"]=895172.85714286,["x"]=-314667.42857143,}, - [2]={["y"]=895337.42857143,["x"]=-314143.14285714,}, - [3]={["y"]=895990.28571429,["x"]=-314036,}, - [4]={["y"]=897730.28571429,["x"]=-315284.57142857,}, - [5]={["y"]=897901.71428571,["x"]=-316284.57142857,}, - [6]={["y"]=897684.57142857,["x"]=-316618.85714286,}, - [7]={["y"]=895173.14285714,["x"]=-314667.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, - [2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, - [3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, - [4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, - [5]={["y"]=895261.71428572,["x"]=-314656,}, - }, - [2] = { - [1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, - [2]={["y"]=897639.71428572,["x"]=-316148,}, - [3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, - [4]={["y"]=895650,["x"]=-314660,}, - [5]={["y"]=895606,["x"]=-314724.85714286,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Vaziani = { - PointsBoundary = { - [1]={["y"]=902122,["x"]=-318163.71428572,}, - [2]={["y"]=902678.57142857,["x"]=-317594,}, - [3]={["y"]=903275.71428571,["x"]=-317405.42857143,}, - [4]={["y"]=903418.57142857,["x"]=-317891.14285714,}, - [5]={["y"]=904292.85714286,["x"]=-318748.28571429,}, - [6]={["y"]=904542,["x"]=-319740.85714286,}, - [7]={["y"]=904042,["x"]=-320166.57142857,}, - [8]={["y"]=902121.42857143,["x"]=-318164.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, - [2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, - [3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, - [4]={["y"]=902294.57142857,["x"]=-318146,}, - [5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_CAUCASUS object. --- @param #AIRBASEPOLICE_CAUCASUS self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_CAUCASUS self -function AIRBASEPOLICE_CAUCASUS:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - - -- -- AnapaVityazevo - -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Batumi - -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Beslan - -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gelendzhik - -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gudauta - -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kobuleti - -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarCenter - -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarPashkovsky - -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Krymsk - -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kutaisi - -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MaykopKhanskaya - -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MineralnyeVody - -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Mozdok - -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Nalchik - -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Novorossiysk - -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SenakiKolkhi - -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SochiAdler - -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Soganlug - -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SukhumiBabushara - -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- TbilisiLochini - -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Vaziani - -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - - - -- Template - -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - return self - -end - - - - ---- @type AIRBASEPOLICE_NEVADA --- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE -AIRBASEPOLICE_NEVADA = { - ClassName = "AIRBASEPOLICE_NEVADA", - Airbases = { - Nellis = { - PointsBoundary = { - [1]={["y"]=-17814.714285714,["x"]=-399823.14285714,}, - [2]={["y"]=-16875.857142857,["x"]=-398763.14285714,}, - [3]={["y"]=-16251.571428571,["x"]=-398988.85714286,}, - [4]={["y"]=-16163,["x"]=-398693.14285714,}, - [5]={["y"]=-16328.714285714,["x"]=-398034.57142857,}, - [6]={["y"]=-15943,["x"]=-397571.71428571,}, - [7]={["y"]=-15711.571428571,["x"]=-397551.71428571,}, - [8]={["y"]=-15748.714285714,["x"]=-396806,}, - [9]={["y"]=-16288.714285714,["x"]=-396517.42857143,}, - [10]={["y"]=-16751.571428571,["x"]=-396308.85714286,}, - [11]={["y"]=-17263,["x"]=-396234.57142857,}, - [12]={["y"]=-17577.285714286,["x"]=-396640.28571429,}, - [13]={["y"]=-17614.428571429,["x"]=-397400.28571429,}, - [14]={["y"]=-19405.857142857,["x"]=-399428.85714286,}, - [15]={["y"]=-19234.428571429,["x"]=-399683.14285714,}, - [16]={["y"]=-18708.714285714,["x"]=-399408.85714286,}, - [17]={["y"]=-18397.285714286,["x"]=-399657.42857143,}, - [18]={["y"]=-17814.428571429,["x"]=-399823.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-18687,["x"]=-399380.28571429,}, - [2]={["y"]=-18620.714285714,["x"]=-399436.85714286,}, - [3]={["y"]=-16217.857142857,["x"]=-396596.85714286,}, - [4]={["y"]=-16300.142857143,["x"]=-396530,}, - [5]={["y"]=-18687,["x"]=-399380.85714286,}, - }, - [2] = { - [1]={["y"]=-18451.571428572,["x"]=-399580.57142857,}, - [2]={["y"]=-18392.142857143,["x"]=-399628.57142857,}, - [3]={["y"]=-16011,["x"]=-396806.85714286,}, - [4]={["y"]=-16074.714285714,["x"]=-396751.71428572,}, - [5]={["y"]=-18451.571428572,["x"]=-399580.85714285,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - McCarran = { - PointsBoundary = { - [1]={["y"]=-29455.285714286,["x"]=-416277.42857142,}, - [2]={["y"]=-28860.142857143,["x"]=-416492,}, - [3]={["y"]=-25044.428571429,["x"]=-416344.85714285,}, - [4]={["y"]=-24580.142857143,["x"]=-415959.14285714,}, - [5]={["y"]=-25073,["x"]=-415630.57142857,}, - [6]={["y"]=-25087.285714286,["x"]=-415130.57142857,}, - [7]={["y"]=-25830.142857143,["x"]=-414866.28571428,}, - [8]={["y"]=-26658.714285715,["x"]=-414880.57142857,}, - [9]={["y"]=-26973,["x"]=-415273.42857142,}, - [10]={["y"]=-27380.142857143,["x"]=-415187.71428571,}, - [11]={["y"]=-27715.857142857,["x"]=-414144.85714285,}, - [12]={["y"]=-27551.571428572,["x"]=-413473.42857142,}, - [13]={["y"]=-28630.142857143,["x"]=-413201.99999999,}, - [14]={["y"]=-29494.428571429,["x"]=-415437.71428571,}, - [15]={["y"]=-29455.571428572,["x"]=-416277.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-29408.428571429,["x"]=-416016.28571428,}, - [2]={["y"]=-29408.142857144,["x"]=-416105.42857142,}, - [3]={["y"]=-24680.714285715,["x"]=-416003.14285713,}, - [4]={["y"]=-24681.857142858,["x"]=-415926.57142856,}, - [5]={["y"]=-29408.42857143,["x"]=-416016.57142856,}, - }, - [2] = { - [1]={["y"]=-28575.571428572,["x"]=-416303.14285713,}, - [2]={["y"]=-28575.571428572,["x"]=-416382.57142856,}, - [3]={["y"]=-25111.000000001,["x"]=-416309.7142857,}, - [4]={["y"]=-25111.000000001,["x"]=-416249.14285713,}, - [5]={["y"]=-28575.571428572,["x"]=-416303.7142857,}, - }, - [3] = { - [1]={["y"]=-29331.000000001,["x"]=-416275.42857141,}, - [2]={["y"]=-29259.000000001,["x"]=-416306.85714284,}, - [3]={["y"]=-28005.571428572,["x"]=-413449.7142857,}, - [4]={["y"]=-28068.714285715,["x"]=-413422.85714284,}, - [5]={["y"]=-29331.000000001,["x"]=-416275.7142857,}, - }, - [4] = { - [1]={["y"]=-29073.285714286,["x"]=-416386.57142856,}, - [2]={["y"]=-28997.285714286,["x"]=-416417.42857141,}, - [3]={["y"]=-27697.571428572,["x"]=-413464.57142856,}, - [4]={["y"]=-27767.857142858,["x"]=-413434.28571427,}, - [5]={["y"]=-29073.000000001,["x"]=-416386.85714284,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Creech = { - PointsBoundary = { - [1]={["y"]=-74522.714285715,["x"]=-360887.99999998,}, - [2]={["y"]=-74197,["x"]=-360556.57142855,}, - [3]={["y"]=-74402.714285715,["x"]=-359639.42857141,}, - [4]={["y"]=-74637,["x"]=-359279.42857141,}, - [5]={["y"]=-75759.857142857,["x"]=-359005.14285712,}, - [6]={["y"]=-75834.142857143,["x"]=-359045.14285712,}, - [7]={["y"]=-75902.714285714,["x"]=-359782.28571427,}, - [8]={["y"]=-76099.857142857,["x"]=-360399.42857141,}, - [9]={["y"]=-77314.142857143,["x"]=-360219.42857141,}, - [10]={["y"]=-77728.428571429,["x"]=-360445.14285713,}, - [11]={["y"]=-77585.571428571,["x"]=-360585.14285713,}, - [12]={["y"]=-76471.285714286,["x"]=-360819.42857141,}, - [13]={["y"]=-76325.571428571,["x"]=-360942.28571427,}, - [14]={["y"]=-74671.857142857,["x"]=-360927.7142857,}, - [15]={["y"]=-74522.714285714,["x"]=-360888.85714284,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-74237.571428571,["x"]=-360591.7142857,}, - [2]={["y"]=-74234.428571429,["x"]=-360493.71428571,}, - [3]={["y"]=-77605.285714286,["x"]=-360399.14285713,}, - [4]={["y"]=-77608.714285715,["x"]=-360498.85714285,}, - [5]={["y"]=-74237.857142857,["x"]=-360591.7142857,}, - }, - [2] = { - [1]={["y"]=-75807.571428572,["x"]=-359073.42857142,}, - [2]={["y"]=-74770.142857144,["x"]=-360581.71428571,}, - [3]={["y"]=-74641.285714287,["x"]=-360585.42857142,}, - [4]={["y"]=-75734.142857144,["x"]=-359023.14285714,}, - [5]={["y"]=-75807.285714287,["x"]=-359073.42857142,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - GroomLake = { - PointsBoundary = { - [1]={["y"]=-88916.714285714,["x"]=-289102.28571425,}, - [2]={["y"]=-87023.571428572,["x"]=-290388.57142857,}, - [3]={["y"]=-85916.428571429,["x"]=-290674.28571428,}, - [4]={["y"]=-87645.000000001,["x"]=-286567.14285714,}, - [5]={["y"]=-88380.714285715,["x"]=-286388.57142857,}, - [6]={["y"]=-89670.714285715,["x"]=-283524.28571428,}, - [7]={["y"]=-89797.857142858,["x"]=-283567.14285714,}, - [8]={["y"]=-88635.000000001,["x"]=-286749.99999999,}, - [9]={["y"]=-89177.857142858,["x"]=-287207.14285714,}, - [10]={["y"]=-89092.142857144,["x"]=-288892.85714285,}, - [11]={["y"]=-88917.000000001,["x"]=-289102.85714285,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-86039.000000001,["x"]=-290606.28571428,}, - [2]={["y"]=-85965.285714287,["x"]=-290573.99999999,}, - [3]={["y"]=-87692.714285715,["x"]=-286634.85714285,}, - [4]={["y"]=-87756.714285715,["x"]=-286663.99999999,}, - [5]={["y"]=-86038.714285715,["x"]=-290606.85714285,}, - }, - [2] = { - [1]={["y"]=-86808.428571429,["x"]=-290375.7142857,}, - [2]={["y"]=-86732.714285715,["x"]=-290344.28571427,}, - [3]={["y"]=-89672.714285714,["x"]=-283546.57142855,}, - [4]={["y"]=-89772.142857143,["x"]=-283587.71428569,}, - [5]={["y"]=-86808.142857143,["x"]=-290375.7142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_NEVADA object. --- @param #AIRBASEPOLICE_NEVADA self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_NEVADA self -function AIRBASEPOLICE_NEVADA:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - --- -- Nellis --- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- McCarran --- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Creech --- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Groom Lake --- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -end - - - - - - \ No newline at end of file diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 79aa2dc6b..50c3851f1 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -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) diff --git a/Moose Development/Moose/Functional/Designate.lua b/Moose Development/Moose/Functional/Designate.lua index 14b8fbb03..7775f78eb 100644 --- a/Moose Development/Moose/Functional/Designate.lua +++ b/Moose Development/Moose/Functional/Designate.lua @@ -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() diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 952f8d496..45bdc0b77 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -302,10 +302,10 @@ do -- DETECTION_BASE -- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @field #boolean Changed Documents if the detected area has changes. -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). - -- @field #number ItemID -- The identifier of the detected area. + -- @field #number ID -- The identifier of the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. - + -- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem. --- DETECTION constructor. -- @param #DETECTION_BASE self @@ -322,7 +322,7 @@ do -- DETECTION_BASE self.DetectionSetGroup = DetectionSetGroup - self.DetectionInterval = 30 + self.RefreshTimeInterval = 30 self:InitDetectVisual( nil ) self:InitDetectOptical( nil ) @@ -344,7 +344,6 @@ do -- DETECTION_BASE -- Create FSM transitions. self:SetStartState( "Stopped" ) - self.CountryID = DetectionSetGroup:GetFirst():GetCountry() self:AddTransition( "Stopped", "Start", "Detecting") @@ -508,7 +507,7 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. function DETECTION_BASE:onafterStart(From,Event,To) - self:__Detect(0.1) + self:__Detect( 1 ) end --- @param #DETECTION_BASE self @@ -529,7 +528,7 @@ do -- DETECTION_BASE --self:E( { DetectionGroupData } ) self:__DetectionGroup( DetectDelay, DetectionGroupData, DetectionTimeStamp ) -- Process each detection asynchronously. self.DetectionCount = self.DetectionCount + 1 - DetectDelay = DetectDelay + 0.1 + DetectDelay = DetectDelay + 1 end end @@ -585,6 +584,7 @@ do -- DETECTION_BASE local DetectionAccepted = true local DetectedObjectName = DetectedObject:getName() + local DetectedObjectType = DetectedObject:getTypeName() local DetectedObjectVec3 = DetectedObject:getPoint() local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z } @@ -598,12 +598,20 @@ do -- DETECTION_BASE local DetectedUnitCategory = DetectedObject:getDesc().category - self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, Distance, DetectedUnitCategory } ) + self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } ) -- Calculate Acceptance DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false +-- if Distance > 15000 then +-- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then +-- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then +-- DetectionAccepted = false +-- end +-- end +-- end + if self.AcceptRange and Distance > self.AcceptRange then DetectionAccepted = false end @@ -692,6 +700,8 @@ do -- DETECTION_BASE self.DetectedObjects[DetectedObjectName].Distance = Distance self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp + self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) + local DetectedUnit = UNIT:FindByName( DetectedObjectName ) DetectedUnits[DetectedObjectName] = DetectedUnit @@ -732,7 +742,7 @@ do -- DETECTION_BASE self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list. end - self:__Detect( self.DetectionInterval ) + self:__Detect( self.RefreshTimeInterval ) end end @@ -912,12 +922,12 @@ do -- DETECTION_BASE --- Set the detection interval time in seconds. -- @param #DETECTION_BASE self - -- @param #number DetectionInterval Interval in seconds. + -- @param #number RefreshTimeInterval Interval in seconds. -- @return #DETECTION_BASE self - function DETECTION_BASE:SetDetectionInterval( DetectionInterval ) + function DETECTION_BASE:SetRefreshTimeInterval( RefreshTimeInterval ) self:F2() - self.DetectionInterval = DetectionInterval + self.RefreshTimeInterval = RefreshTimeInterval return self end @@ -940,6 +950,24 @@ do -- DETECTION_BASE end + do -- Intercept Point + + --- Set the parameters to calculate to optimal intercept point. + -- @param #DETECTION_BASE self + -- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false. + -- @param #number IntereptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne. + -- @return #DETECTION_BASE self + function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay ) + self:F2() + + self.Intercept = Intercept + self.InterceptDelay = InterceptDelay + + return self + end + + end + do -- Accept / Reject detected units --- Accept detections if within a range in meters. @@ -956,15 +984,20 @@ do -- DETECTION_BASE --- Accept detections if within the specified zone(s). -- @param #DETECTION_BASE self - -- @param AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. + -- @param Core.Zone#ZONE_BASE AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetAcceptZones( AcceptZones ) self:F2() if type( AcceptZones ) == "table" then - self.AcceptZones = AcceptZones + if AcceptZones.ClassName and AcceptZones:IsInstanceOf( ZONE_BASE ) then + self.AcceptZones = { AcceptZones } + else + self.AcceptZones = AcceptZones + end else - self.AcceptZones = { AcceptZones } + self:E( { "AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object", AcceptZones } ) + error() end return self @@ -972,15 +1005,20 @@ do -- DETECTION_BASE --- Reject detections if within the specified zone(s). -- @param #DETECTION_BASE self - -- @param RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. + -- @param Core.Zone#ZONE_BASE RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetRejectZones( RejectZones ) self:F2() if type( RejectZones ) == "table" then - self.RejectZones = RejectZones + if RejectZones.ClassName and RejectZones:IsInstanceOf( ZONE_BASE ) then + self.RejectZones = { RejectZones } + else + self.RejectZones = RejectZones + end else - self.RejectZones = { RejectZones } + self:E( { "RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object", RejectZones } ) + error() end return self @@ -1064,15 +1102,15 @@ do -- DETECTION_BASE function DETECTION_BASE:AddChangeItem( DetectedItem, ChangeCode, ItemUnitType ) DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID + local ID = DetectedItem.ID DetectedItem.Changes = DetectedItem.Changes or {} DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} - DetectedItem.Changes[ChangeCode].ItemID = ItemID + DetectedItem.Changes[ChangeCode].ID = ID DetectedItem.Changes[ChangeCode].ItemUnitType = ItemUnitType - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ItemUnitType } ) - + self:E( { "Change on Detection Item:", DetectedItem.ID, ChangeCode, ItemUnitType } ) + return self end @@ -1086,15 +1124,15 @@ do -- DETECTION_BASE function DETECTION_BASE:AddChangeUnit( DetectedItem, ChangeCode, ChangeUnitType ) DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID + local ID = DetectedItem.ID DetectedItem.Changes = DetectedItem.Changes or {} DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] or 0 DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedItem.Changes[ChangeCode].ItemID = ItemID + DetectedItem.Changes[ChangeCode].ID = ID - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ChangeUnitType } ) + self:E( { "Change on Detection Item:", DetectedItem.ID, ChangeCode, ChangeUnitType } ) return self end @@ -1102,11 +1140,32 @@ do -- DETECTION_BASE end - do -- Threat + do -- Friendly calculations + + --- This will allow during friendly search any recce or detection unit to be also considered as a friendly. + -- By default, recce aren't considered friendly, because that would mean that a recce would be also an attacking friendly, + -- and this is wrong. + -- However, in a CAP situation, when the CAP is part of an EWR network, the CAP is also an attacker. + -- This, this method allows to register for a detection the CAP unit name prefixes to be considered CAP. + -- @param #DETECTION_BASE self + -- @param #string FriendlyPrefixes A string or a list of prefixes. + -- @return #DETECTION_BASE + function DETECTION_BASE:SetFriendlyPrefixes( FriendlyPrefixes ) + + self.FriendlyPrefixes = self.FriendlyPrefixes or {} + if type( FriendlyPrefixes ) ~= "table" then + FriendlyPrefixes = { FriendlyPrefixes } + end + for PrefixID, Prefix in pairs( FriendlyPrefixes ) do + self:F( { FriendlyPrefix = Prefix } ) + self.FriendlyPrefixes[Prefix] = Prefix + end + return self + end --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self - -- @return #boolean trhe if there are friendlies nearby + -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsFriendliesNearBy( DetectedItem ) return DetectedItem.FriendliesNearBy ~= nil or false @@ -1119,10 +1178,35 @@ do -- DETECTION_BASE return DetectedItem.FriendliesNearBy end - - --- Returns friendly units nearby the FAC units sorted per distance ... + + --- Filters friendly units by unit category. -- @param #DETECTION_BASE self - -- @return #map<#number,Wrapper.Unit#UNIT> The map of Friendly UNITs. + -- @param FriendliesCategory + -- @return #DETECTION_BASE + function DETECTION_BASE:FilterFriendliesCategory( FriendliesCategory ) + self.FriendliesCategory = FriendliesCategory + return self + end + + --- Returns if there are friendlies nearby the intercept ... + -- @param #DETECTION_BASE self + -- @return #boolean trhe if there are friendlies near the intercept. + function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem ) + + return DetectedItem.FriendliesNearIntercept ~= nil or false + end + + --- Returns friendly units nearby the intercept point ... + -- @param #DETECTION_BASE self + -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. + function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem ) + + return DetectedItem.FriendliesNearIntercept + end + + --- Returns the distance used to identify friendlies near the deteted item ... + -- @param #DETECTION_BASE self + -- @return #number The distance. function DETECTION_BASE:GetFriendliesDistance( DetectedItem ) return DetectedItem.FriendliesDistance @@ -1151,17 +1235,20 @@ do -- DETECTION_BASE local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = ReportGroupData.DetectedItem.Set - local DetectedUnit = DetectedSet:GetFirst() + local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT DetectedItem.FriendliesNearBy = nil - if DetectedUnit then + -- We need to ensure that the DetectedUnit is alive! + if DetectedUnit and DetectedUnit:IsAlive() then + local DetectedUnitCoord = DetectedUnit:GetCoordinate() + local InterceptCoord = ReportGroupData.InterceptCoord or DetectedUnitCoord local SphereSearch = { id = world.VolumeType.SPHERE, params = { - point = DetectedUnit:GetVec3(), + point = InterceptCoord:GetVec3(), radius = self.FriendliesRange, } @@ -1175,7 +1262,8 @@ do -- DETECTION_BASE local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = ReportGroupData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - local CenterCoord = DetectedUnit:GetCoordinate() + local DetectedUnitCoord = DetectedUnit:GetCoordinate() + local InterceptCoord = ReportGroupData.InterceptCoord or DetectedUnitCoord local ReportSetGroup = ReportGroupData.ReportSetGroup local EnemyCoalition = DetectedUnit:GetCoalition() @@ -1184,18 +1272,41 @@ do -- DETECTION_BASE local FoundUnitName = FoundDCSUnit:getName() local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() local EnemyUnitName = DetectedUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + self:T( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + if FoundUnitInReportSetGroup == true then + -- If the recce was part of the friendlies found, then check if the recce is part of the allowed friendly unit prefixes. + for PrefixID, Prefix in pairs( self.FriendlyPrefixes or {} ) do + self:F( { "FriendlyPrefix:", Prefix } ) + -- In case a match is found (so a recce unit name is part of the friendly prefixes), then report that recce to be part of the friendlies. + -- This is important if CAP planes (so planes using their own radar) to be scanning for targets as part of the EWR network. + -- But CAP planes are also attackers, so they need to be considered friendlies too! + -- I chose to use prefixes because it is the fastest way to check. + if string.find( FoundUnitName, Prefix:gsub ("-", "%%-"), 1 ) then + FoundUnitInReportSetGroup = false + break + end + end + end + self:F( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} local FriendlyUnit = UNIT:Find( FoundDCSUnit ) local FriendlyUnitName = FriendlyUnit:GetName() - DetectedItem.FriendliesNearBy[FriendlyUnitName] = FriendlyUnit - local Distance = CenterCoord:Get2DDistance( FriendlyUnit:GetCoordinate() ) - DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} - DetectedItem.FriendliesDistance[Distance] = FriendlyUnit + local FriendlyUnitCategory = FriendlyUnit:GetDesc().category + self:T( { FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) + + --if ( not self.FriendliesCategory ) or ( self.FriendliesCategory and ( self.FriendliesCategory == FriendlyUnitCategory ) ) then + DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} + DetectedItem.FriendliesNearBy[FriendlyUnitName] = FriendlyUnit + local Distance = DetectedUnitCoord:Get2DDistance( FriendlyUnit:GetCoordinate() ) + DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} + DetectedItem.FriendliesDistance[Distance] = FriendlyUnit + self:T( { FriendlyUnitName = FriendlyUnitName, Distance = Distance } ) + --end return true end @@ -1211,22 +1322,27 @@ do -- DETECTION_BASE --- @param Wrapper.Unit#UNIT PlayerUnit function( PlayerUnitName ) local PlayerUnit = UNIT:FindByName( PlayerUnitName ) - + if PlayerUnit and PlayerUnit:IsInZone(DetectionZone) then + + local PlayerUnitCategory = PlayerUnit:GetDesc().category - DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} - local PlayerUnitName = PlayerUnit:GetName() - - DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {} - DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit - - DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} - DetectedItem.FriendliesNearBy[PlayerUnitName] = PlayerUnit - - local CenterCoord = DetectedUnit:GetCoordinate() - local Distance = CenterCoord:Get2DDistance( PlayerUnit:GetCoordinate() ) - DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} - DetectedItem.FriendliesDistance[Distance] = PlayerUnit + if ( not self.FriendliesCategory ) or ( self.FriendliesCategory and ( self.FriendliesCategory == PlayerUnitCategory ) ) then + + DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} + local PlayerUnitName = PlayerUnit:GetName() + + DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {} + DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit + + DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} + DetectedItem.FriendliesNearBy[PlayerUnitName] = PlayerUnit + + local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() ) + DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} + DetectedItem.FriendliesDistance[Distance] = PlayerUnit + + end end end ) @@ -1240,19 +1356,23 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE.DetectedObject DetectedObject -- @return #boolean true if already identified. function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - self:F3( DetectedObject.Name ) + --self:F3( DetectedObject.Name ) local DetectedObjectName = DetectedObject.Name - local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true - self:T3( DetectedObjectIdentified ) - return DetectedObjectIdentified + if DetectedObjectName then + local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true + self:T3( DetectedObjectIdentified ) + return DetectedObjectIdentified + else + return nil + end end --- Identifies a detected object during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - self:F( { "Identified:", DetectedObject.Name } ) + --self:F( { "Identified:", DetectedObject.Name } ) local DetectedObjectName = DetectedObject.Name self.DetectedObjectsIdentified[DetectedObjectName] = true @@ -1279,16 +1399,18 @@ do -- DETECTION_BASE -- @param #string ObjectName -- @return #DETECTION_BASE.DetectedObject function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F2( ObjectName ) + --self:F2( ObjectName ) if ObjectName then local DetectedObject = self.DetectedObjects[ObjectName] - - -- Only return detected objects that are alive! - local DetectedUnit = UNIT:FindByName( ObjectName ) - if DetectedUnit and DetectedUnit:IsAlive() then - if self:IsDetectedObjectIdentified( DetectedObject ) == false then - return DetectedObject + + if DetectedObject then + -- Only return detected objects that are alive! + local DetectedUnit = UNIT:FindByName( ObjectName ) + if DetectedUnit and DetectedUnit:IsAlive() then + if self:IsDetectedObjectIdentified( DetectedObject ) == false then + return DetectedObject + end end end end @@ -1296,6 +1418,34 @@ do -- DETECTION_BASE return nil end + + --- Gets a detected unit type name, taking into account the detection results. + -- @param #DETECTION_BASE self + -- @param Wrapper.Unit#UNIT DetectedUnit + -- @return #string The type name + function DETECTION_BASE:GetDetectedUnitTypeName( DetectedUnit ) + --self:F2( ObjectName ) + + if DetectedUnit and DetectedUnit:IsAlive() then + local DetectedUnitName = DetectedUnit:GetName() + local DetectedObject = self.DetectedObjects[DetectedUnitName] + + if DetectedObject then + if DetectedObject.KnowType then + return DetectedUnit:GetTypeName() + else + return "Unknown" + end + else + return "Unknown" + end + else + return "Dead:" .. DetectedUnit:GetName() + end + + return "Undetected:" .. DetectedUnit:GetName() + end + --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. @@ -1438,6 +1588,7 @@ do -- DETECTION_BASE for UnitName, UnitData in pairs( DetectedItem.Set:GetSet() ) do local DetectedObject = self.DetectedObjects[UnitName] + self:F({UnitName = UnitName, IsDetected = DetectedObject.IsDetected}) if DetectedObject.IsDetected then IsDetected = true break @@ -1459,37 +1610,6 @@ do -- DETECTION_BASE return DetectedItem.IsDetected end - do -- Coordinates - - --- Get the COORDINATE of a detection item using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return Core.Point#COORDINATE - function DETECTION_BASE:GetDetectedItemCoordinate( Index ) - - -- If the Zone is set, return the coordinate of the Zone. - local DetectedItemSet = self:GetDetectedSet( Index ) - local FirstUnit = DetectedItemSet:GetFirst() - - local DetectedZone = self:GetDetectedItemZone( Index ) - if DetectedZone then - local Coordinate = DetectedZone:GetPointVec2() - Coordinate:SetHeading(FirstUnit:GetHeading()) - Coordinate:SetAlt( FirstUnit:GetAltitude() ) - return Coordinate - end - - -- If no Zone is set, return the coordinate of the first unit in the Set - if FirstUnit then - local Coordinate = FirstUnit:GetPointVec3() - FirstUnit:SetHeading(FirstUnit:GetHeading()) - return Coordinate - end - - return nil - end - - end do -- Zones @@ -1511,6 +1631,79 @@ do -- DETECTION_BASE end + + --- Set the detected item coordinate. + -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem The DetectedItem to set the coordinate at. + -- @param Core.Point#COORDINATE Coordinate The coordinate to set the last know detected position at. + -- @param Wrapper.Unit#UNIT DetectedItemUnit The unit to set the heading and altitude from. + -- @return #DETECTION_BASE + function DETECTION_BASE:SetDetectedItemCoordinate( DetectedItem, Coordinate, DetectedItemUnit ) + self:F( { Coordinate = Coordinate } ) + + if DetectedItem then + if DetectedItemUnit then + DetectedItem.Coordinate = Coordinate + DetectedItem.Coordinate:SetHeading( DetectedItemUnit:GetHeading() ) + DetectedItem.Coordinate.y = DetectedItemUnit:GetAltitude() + DetectedItem.Coordinate:SetVelocity( DetectedItemUnit:GetVelocityMPS() ) + end + end + end + + + --- Get the detected item coordinate. + -- @param #DETECTION_BASE self + -- @param #number Index + -- @return Core.Point#COORDINATE + function DETECTION_BASE:GetDetectedItemCoordinate( Index ) + self:F( { Index = Index } ) + + local DetectedItem = self:GetDetectedItem( Index ) + + if DetectedItem then + return DetectedItem.Coordinate + end + + return nil + end + + --- Set the detected item threatlevel. + -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threatlevel for. + -- @return #DETECTION_BASE + function DETECTION_BASE:SetDetectedItemThreatLevel( DetectedItem ) + + local DetectedSet = DetectedItem.Set + + if DetectedItem then + DetectedItem.ThreatLevel, DetectedItem.ThreatText = DetectedSet:CalculateThreatLevelA2G() + end + end + + + + --- Get the detected item coordinate. + -- @param #DETECTION_BASE self + -- @param #number Index + -- @return #number ThreatLevel + function DETECTION_BASE:GetDetectedItemThreatLevel( Index ) + self:F( { Index = Index } ) + + local DetectedItem = self:GetDetectedItem( Index ) + + if DetectedItem then + return DetectedItem.ThreatLevel or 0, DetectedItem.ThreatText or "" + end + + return nil, "" + end + + + + + + --- Menu of a detected item using a given numeric index. -- @param #DETECTION_BASE self -- @param Index @@ -1524,14 +1717,17 @@ do -- DETECTION_BASE --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_BASE self -- @param Index - -- @return #string - function DETECTION_BASE:DetectedItemReportSummary( Index, AttackGroup ) + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. + -- @param Core.Settings#SETTINGS Settings Message formatting settings to use. + -- @return Core.Report#REPORT + function DETECTION_BASE:DetectedItemReportSummary( Index, AttackGroup, Settings ) self:F( Index ) return nil end --- Report detailed of a detectedion result. -- @param #DETECTION_BASE self + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_BASE:DetectedReportDetailed( AttackGroup ) self:F() @@ -1613,7 +1809,7 @@ do -- DETECTION_UNITS if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end @@ -1623,7 +1819,7 @@ do -- DETECTION_UNITS if ChangeCode == "RU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end @@ -1666,6 +1862,7 @@ do -- DETECTION_UNITS -- Update the detection with the new data provided. DetectedItem.TypeName = DetectedUnit:GetTypeName() + DetectedItem.CategoryName = DetectedUnit:GetCategoryName() DetectedItem.Name = DetectedObject.Name DetectedItem.IsVisible = DetectedObject.IsVisible DetectedItem.LastTime = DetectedObject.LastTime @@ -1720,8 +1917,14 @@ do -- DETECTION_UNITS local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set + -- Set the last known coordinate. + local DetectedFirstUnit = DetectedSet:GetFirst() + local DetectedFirstUnitCoord = DetectedFirstUnit:GetCoordinate() + self:SetDetectedItemCoordinate( DetectedItem, DetectedFirstUnitCoord, DetectedFirstUnit ) + self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table --self:NearestFAC( DetectedItem ) + end end @@ -1743,21 +1946,14 @@ do -- DETECTION_UNITS local UnitDistanceText = "" local UnitCategoryText = "" - local DetectedItemUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - - if DetectedItemUnit and DetectedItemUnit:IsAlive() then - self:T(DetectedItemUnit) - - local DetectedItemCoordinate = DetectedItemUnit:GetCoordinate() - local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) - - ReportSummary = string.format( - "%s - %s", - DetectedItemID, - DetectedItemCoordText - ) - end - + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( Index ) + local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) + + ReportSummary = string.format( + "%s - %s", + DetectedItemID, + DetectedItemCoordText + ) self:T( ReportSummary ) return ReportSummary @@ -1767,82 +1963,71 @@ do -- DETECTION_UNITS --- Report summary of a DetectedItem using a given numeric index. -- @param #DETECTION_UNITS self -- @param Index - -- @return #string - function DETECTION_UNITS:DetectedItemReportSummary( Index, AttackGroup ) + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. + -- @param Core.Settings#SETTINGS Settings Message formatting settings to use. + -- @return Core.Report#REPORT The report of the detection items. + function DETECTION_UNITS:DetectedItemReportSummary( Index, AttackGroup, Settings ) self:F( { Index, self.DetectedItems } ) local DetectedItem = self:GetDetectedItem( Index ) - local DetectedSet = self:GetDetectedSet( Index ) local DetectedItemID = self:GetDetectedItemID( Index ) - self:T( DetectedSet ) - if DetectedSet then + if DetectedItem then local ReportSummary = "" local UnitDistanceText = "" local UnitCategoryText = "" - local DetectedItemUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - - if DetectedItemUnit and DetectedItemUnit:IsAlive() then - self:T(DetectedItemUnit) - - - if DetectedItem.KnowType then - local UnitCategoryName = DetectedItemUnit:GetCategoryName() - if UnitCategoryName then - UnitCategoryText = UnitCategoryName - end - if DetectedItem.TypeName then - UnitCategoryText = UnitCategoryText .. " (" .. DetectedItem.TypeName .. ")" - end - else - UnitCategoryText = "Unknown" + if DetectedItem.KnowType then + local UnitCategoryName = DetectedItem.CategoryName + if UnitCategoryName then + UnitCategoryText = UnitCategoryName end - - if DetectedItem.KnowDistance then - if DetectedItem.IsVisible then - UnitDistanceText = " at " .. string.format( "%.2f", DetectedItem.Distance ) .. " km" - end - else - if DetectedItem.IsVisible then - UnitDistanceText = " at +/- " .. string.format( "%.0f", DetectedItem.Distance ) .. " km" - end + if DetectedItem.TypeName then + UnitCategoryText = UnitCategoryText .. " (" .. DetectedItem.TypeName .. ")" end - - local DetectedItemCoordinate = DetectedItemUnit:GetCoordinate() - local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) - - local ThreatLevelA2G = DetectedItemUnit:GetThreatLevel( DetectedItem ) - - ReportSummary = string.format( - "%s - %s - Threat:[%s](%2d) - %s%s", - DetectedItemID, - DetectedItemCoordText, - string.rep( "■", ThreatLevelA2G ), - ThreatLevelA2G, - UnitCategoryText, - UnitDistanceText - ) + else + UnitCategoryText = "Unknown" end - self:T( ReportSummary ) - - return ReportSummary + if DetectedItem.KnowDistance then + if DetectedItem.IsVisible then + UnitDistanceText = " at " .. string.format( "%.2f", DetectedItem.Distance ) .. " km" + end + else + if DetectedItem.IsVisible then + UnitDistanceText = " at +/- " .. string.format( "%.0f", DetectedItem.Distance ) .. " km" + end + end + + --TODO: solve Index reference + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( Index ) + local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) + + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( Index ) + + local Report = REPORT:New() + Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) + Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ) ) ) + Report:Add( string.format("Type: %s%s", UnitCategoryText, UnitDistanceText ) ) + return Report end + return nil end --- Report detailed of a detection result. -- @param #DETECTION_UNITS self + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_UNITS:DetectedReportDetailed( AttackGroup ) self:F() - local Report = REPORT:New( "Detected units:" ) + local Report = REPORT:New() for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem local ReportSummary = self:DetectedItemReportSummary( DetectedItemID, AttackGroup ) - Report:Add( ReportSummary ) + Report:SetTitle( "Detected units:" ) + Report:Add( ReportSummary:Text() ) end local ReportText = Report:Text() @@ -1885,7 +2070,7 @@ do -- DETECTION_TYPES return self end - + --- Make text documenting the changes of the detected zone. -- @param #DETECTION_TYPES self -- @param #DETECTION_TYPES.DetectedItem DetectedItem @@ -1900,7 +2085,7 @@ do -- DETECTION_TYPES if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end @@ -1910,7 +2095,7 @@ do -- DETECTION_TYPES if ChangeCode == "RU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end @@ -1990,10 +2175,17 @@ do -- DETECTION_TYPES local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set + -- Set the last known coordinate. + local DetectedFirstUnit = DetectedSet:GetFirst() + local DetectedUnitCoord = DetectedFirstUnit:GetCoordinate() + self:SetDetectedItemCoordinate( DetectedItem, DetectedUnitCoord, DetectedFirstUnit ) + self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table --self:NearestFAC( DetectedItem ) end + + end --- Menu of a DetectedItem using a given numeric index. @@ -2004,20 +2196,13 @@ do -- DETECTION_TYPES self:F( DetectedTypeName ) local DetectedItem = self:GetDetectedItem( DetectedTypeName ) - local DetectedSet = self:GetDetectedSet( DetectedTypeName ) local DetectedItemID = self:GetDetectedItemID( DetectedTypeName ) - self:T( DetectedItem ) if DetectedItem then - local DetectedItemUnit = DetectedSet:GetFirst() - - local DetectedItemCoordinate = DetectedItemUnit:GetCoordinate() + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedTypeName ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) - --self:E( { DetectedItemID, - -- DetectedItemCoordText } ) - local ReportSummary = string.format( "%s - %s", DetectedItemID, @@ -2032,8 +2217,10 @@ do -- DETECTION_TYPES --- Report summary of a DetectedItem using a given numeric index. -- @param #DETECTION_TYPES self -- @param Index - -- @return #string - function DETECTION_TYPES:DetectedItemReportSummary( DetectedTypeName, AttackGroup ) + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. + -- @param Core.Settings#SETTINGS Settings Message formatting settings to use. + -- @return Core.Report#REPORT The report of the detection items. + function DETECTION_TYPES:DetectedItemReportSummary( DetectedTypeName, AttackGroup, Settings ) self:F( DetectedTypeName ) local DetectedItem = self:GetDetectedItem( DetectedTypeName ) @@ -2043,41 +2230,34 @@ do -- DETECTION_TYPES self:T( DetectedItem ) if DetectedItem then - local ThreatLevelA2G = DetectedSet:CalculateThreatLevelA2G() + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedTypeName ) local DetectedItemsCount = DetectedSet:Count() local DetectedItemType = DetectedItem.TypeName - local DetectedItemUnit = DetectedSet:GetFirst() + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedTypeName ) + local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) - local DetectedItemCoordinate = DetectedItemUnit:GetCoordinate() - local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) - - local ReportSummary = string.format( - "%s - %s - Threat:[%s](%2d) - %2d of %s", - DetectedItemID, - DetectedItemCoordText, - string.rep( "■", ThreatLevelA2G ), - ThreatLevelA2G, - DetectedItemsCount, - DetectedItemType - ) - self:T( ReportSummary ) - - return ReportSummary + local Report = REPORT:New() + Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) + Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ) ) ) + Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemType ) ) + return Report end end --- Report detailed of a detection result. -- @param #DETECTION_TYPES self + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_TYPES:DetectedReportDetailed( AttackGroup ) self:F() - local Report = REPORT:New( "Detected types:" ) + local Report = REPORT:New() for DetectedItemTypeName, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem local ReportSummary = self:DetectedItemReportSummary( DetectedItemTypeName, AttackGroup ) - Report:Add( ReportSummary ) + Report:SetTitle( "Detected types:" ) + Report:Add( ReportSummary:Text() ) end local ReportText = Report:Text() @@ -2153,6 +2333,7 @@ do -- DETECTION_AREAS return self end + --- Menu of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param Index @@ -2186,8 +2367,10 @@ do -- DETECTION_AREAS --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param Index - -- @return #string - function DETECTION_AREAS:DetectedItemReportSummary( Index, AttackGroup ) + -- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for. + -- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use. + -- @return Core.Report#REPORT The report of the detection items. + function DETECTION_AREAS:DetectedItemReportSummary( Index, AttackGroup, Settings ) self:F( Index ) local DetectedItem = self:GetDetectedItem( Index ) @@ -2199,23 +2382,18 @@ do -- DETECTION_AREAS local DetectedZone = self:GetDetectedItemZone( Index ) local DetectedItemCoordinate = DetectedZone:GetCoordinate() - local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup ) + local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) - local ThreatLevelA2G = self:GetTreatLevelA2G( DetectedItem ) + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( Index ) local DetectedItemsCount = DetectedSet:Count() local DetectedItemsTypes = DetectedSet:GetTypeNames() - local ReportSummary = string.format( - "%s - %s - Threat:[%s](%2d)\n %2d of %s", - DetectedItemID, - DetectedItemCoordText, - string.rep( "■", ThreatLevelA2G ), - ThreatLevelA2G, - DetectedItemsCount, - DetectedItemsTypes - ) + local Report = REPORT:New() + Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) + Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ) ) ) + Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) ) - return ReportSummary + return Report end return nil @@ -2223,15 +2401,17 @@ do -- DETECTION_AREAS --- Report detailed of a detection result. -- @param #DETECTION_AREAS self + -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string - function DETECTION_AREAS:DetectedReportDetailed( AttackGroup) --R2.1 Fixed missing report + function DETECTION_AREAS:DetectedReportDetailed( AttackGroup ) --R2.1 Fixed missing report self:F() - local Report = REPORT:New( "Detected areas:" ) + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem local ReportSummary = self:DetectedItemReportSummary( DetectedItemIndex, AttackGroup ) - Report:Add( ReportSummary ) + Report:SetTitle( "Detected areas:" ) + Report:Add( ReportSummary:Text() ) end local ReportText = Report:Text() @@ -2240,65 +2420,60 @@ do -- DETECTION_AREAS end - --- Calculate the maxium A2G threat level of the DetectedItem. + --- Calculate the optimal intercept point of the DetectedItem. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem - function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedItem ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedItem.Set:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end + function DETECTION_AREAS:CalculateIntercept( DetectedItem ) + + local DetectedCoord = DetectedItem.Coordinate + local DetectedSpeed = DetectedCoord:GetVelocity() + local DetectedHeading = DetectedCoord:GetHeading() + + if self.Intercept then + local DetectedSet = DetectedItem.Set + -- todo: speed - self:T3( MaxThreatLevelA2G ) - DetectedItem.MaxThreatLevelA2G = MaxThreatLevelA2G + local TranslateDistance = DetectedSpeed * self.InterceptDelay + + local InterceptCoord = DetectedCoord:Translate( TranslateDistance, DetectedHeading ) + + DetectedItem.InterceptCoord = InterceptCoord + else + DetectedItem.InterceptCoord = DetectedCoord + end end + --- Find the nearest FAC of the DetectedItem. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_AREAS:NearestFAC( DetectedItem ) - local NearestFAC = nil - local MinDistance = 1000000000 -- Units are not further than 1000000 km away from an area :-) + local NearestRecce = nil + local DistanceRecce = 1000000000 -- Units are not further than 1000000 km away from an area :-) - for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Wrapper.Unit#UNIT - if FACUnit:IsActive() then - local Vec3 = FACUnit:GetVec3() - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) - if Distance < MinDistance then - MinDistance = Distance - NearestFAC = FACUnit + for RecceGroupName, RecceGroup in pairs( self.DetectionSetGroup:GetSet() ) do + if RecceGroup and RecceGroup:IsAlive() then + for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do + if RecceUnit:IsActive() then + local RecceUnitCoord = RecceUnit:GetCoordinate() + local Distance = RecceUnitCoord:Get2DDistance( self:GetDetectedItemCoordinate( DetectedItem.Index ) ) + if Distance < DistanceRecce then + DistanceRecce = Distance + NearestRecce = RecceUnit + end end end end end - DetectedItem.NearestFAC = NearestFAC + DetectedItem.NearestFAC = NearestRecce + DetectedItem.DistanceRecce = DistanceRecce end - --- Returns the A2G threat level of the units in the DetectedItem - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #number a scale from 0 to 10. - function DETECTION_AREAS:GetTreatLevelA2G( DetectedItem ) - - self:T3( DetectedItem.MaxThreatLevelA2G ) - return DetectedItem.MaxThreatLevelA2G - end - - - --- Smoke the detected units -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self @@ -2361,39 +2536,39 @@ do -- DETECTION_AREAS for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.ItemID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." + MT[#MT+1] = "Detected new area " .. ChangeData.ID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." end if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". Removed the center target." + MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". Removed the center target." end if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "." + MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "." end if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.ItemID .. ". No more targets in this area." + MT[#MT+1] = "Removed old area " .. ChangeData.ID .. ". No more targets in this area." end if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = "Detected for area " .. ChangeData.ItemID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + MT[#MT+1] = "Detected for area " .. ChangeData.ID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." end if ChangeCode == "RU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then + if ChangeUnitType ~= "ID" then MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = "Removed for area " .. ChangeData.ItemID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + MT[#MT+1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." end end @@ -2443,13 +2618,14 @@ do -- DETECTION_AREAS -- First remove the center unit from the set. DetectedSet:RemoveUnitsByName( DetectedItem.Zone.ZoneUNIT.UnitName ) - self:AddChangeItem( DetectedItem, 'RAU', "Dummy" ) + self:AddChangeItem( DetectedItem, 'RAU', self:GetDetectedUnitTypeName( DetectedItem.Zone.ZoneUNIT ) ) -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. -- If the DetectedUnit was already identified, DetectedObject will be nil. @@ -2462,13 +2638,13 @@ do -- DETECTION_AREAS -- Assign the Unit as the new center unit of the detected area. DetectedItem.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - self:AddChangeItem( DetectedItem, "AAU", DetectedItem.Zone.ZoneUNIT:GetTypeName() ) + self:AddChangeItem( DetectedItem, "AAU", DetectedUnitTypeName ) -- We don't need to add the DetectedObject to the area set, because it is already there ... break else DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedItem, "RU", DetectedUnit:GetTypeName() ) + self:AddChangeUnit( DetectedItem, "RU", DetectedUnitTypeName ) end end end @@ -2484,13 +2660,15 @@ do -- DETECTION_AREAS for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT + local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) + local DetectedObject = nil if DetectedUnit:IsAlive() then --self:E(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then - + -- Check if the DetectedUnit is within the DetectedItem.Zone if DetectedUnit:IsInZone( DetectedItem.Zone ) then @@ -2500,7 +2678,7 @@ do -- DETECTION_AREAS else -- No, the DetectedUnit is not within the DetectedItem.Zone, remove DetectedUnit from the Set. DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedItem, "RU", DetectedUnit:GetTypeName() ) + self:AddChangeUnit( DetectedItem, "RU", DetectedUnitTypeName ) end else @@ -2537,6 +2715,7 @@ do -- DETECTION_AREAS -- We found an unidentified unit outside of any existing detection area. local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT + local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) local AddedToDetectionArea = false @@ -2544,13 +2723,13 @@ do -- DETECTION_AREAS local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem if DetectedItem then - self:T( "Detection Area #" .. DetectedItem.ItemID ) + self:T( "Detection Area #" .. DetectedItem.ID ) local DetectedSet = DetectedItem.Set if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedItem.Zone ) then self:IdentifyDetectedObject( DetectedObject ) DetectedSet:AddUnit( DetectedUnit ) AddedToDetectionArea = true - self:AddChangeUnit( DetectedItem, "AU", DetectedUnit:GetTypeName() ) + self:AddChangeUnit( DetectedItem, "AU", DetectedUnitTypeName ) end end end @@ -2564,7 +2743,7 @@ do -- DETECTION_AREAS ) --self:E( DetectedItem.Zone.ZoneUNIT.UnitName ) DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeItem( DetectedItem, "AA", DetectedUnit:GetTypeName() ) + self:AddChangeItem( DetectedItem, "AA", DetectedUnitTypeName ) end end end @@ -2576,11 +2755,19 @@ do -- DETECTION_AREAS local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set + local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedZone = DetectedItem.Zone + + -- Set the last known coordinate to the detection item. + local DetectedZoneCoord = DetectedZone:GetCoordinate() + self:SetDetectedItemCoordinate( DetectedItem, DetectedZoneCoord, DetectedFirstUnit ) + + self:CalculateIntercept( DetectedItem ) self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedItem ) -- Calculate A2G threat level + self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level self:NearestFAC( DetectedItem ) + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then DetectedZone.ZoneUNIT:SmokeRed() @@ -2592,7 +2779,7 @@ do -- DETECTION_AREAS --- @param Wrapper.Unit#UNIT DetectedUnit function( DetectedUnit ) if DetectedUnit:IsAlive() then - --self:T( "Detected Set #" .. DetectedItem.ItemID .. ":" .. DetectedUnit:GetName() ) + --self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then DetectedUnit:FlareGreen() end @@ -2610,6 +2797,7 @@ do -- DETECTION_AREAS end if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then + self.CountryID = DetectedSet:GetFirst():GetCountry() DetectedZone:BoundZone( 12, self.CountryID ) end end diff --git a/Moose Development/Moose/Functional/Escort.lua b/Moose Development/Moose/Functional/Escort.lua index 1a96cb31e..66496f973 100644 --- a/Moose Development/Moose/Functional/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -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 diff --git a/Moose Development/Moose/Functional/MissileTrainer.lua b/Moose Development/Moose/Functional/MissileTrainer.lua index ccceb3958..b8511867d 100644 --- a/Moose Development/Moose/Functional/MissileTrainer.lua +++ b/Moose Development/Moose/Functional/MissileTrainer.lua @@ -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 diff --git a/Moose Development/Moose/Functional/Protect.lua b/Moose Development/Moose/Functional/Protect.lua new file mode 100644 index 000000000..7b163ea2b --- /dev/null +++ b/Moose Development/Moose/Functional/Protect.lua @@ -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 + + + + + diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua new file mode 100644 index 000000000..ab553a06f --- /dev/null +++ b/Moose Development/Moose/Functional/RAT.lua @@ -0,0 +1,4272 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- **Functional** - Create random airtraffic in your missions. +-- +-- ![Banner Image](..\Presentations\RAT\RAT.png) +-- +-- ==== +-- +-- The aim of the RAT class is to fill the empty DCS world with randomized air traffic and bring more life to your airports. +-- +-- In particular, it is designed to spawn AI air units at random airports. These units will be assigned a random flight path to another random airport on the map. +-- +-- Even the mission designer will not know where aircraft will be spawned and which route they follow. +-- +-- ## Features +-- +-- * Very simple interface. Just one unit and two lines of Lua code needed to fill your map. +-- * High degree of randomization. Aircraft will spawn at random airports, have random routes and random destinations. +-- * Specific departure and/or destination airports can be chosen. +-- * Departure and destination airports can be restricted by coalition. +-- * Planes and helicopters supported. Helicopters can also be send to FARPs and ships. +-- * Units can also be spawned in air within pre-defined zones of the map. +-- * Aircraft will be removed when they arrive at their destination (or get stuck on the ground). +-- * When a unit is removed a new unit with a different flight plan is respawned. +-- * Aircraft can report their status during the route. +-- * All of the above can be customized by the user if necessary. +-- * All current (Caucasus, Nevada, Normandy) and future maps are supported. +-- +-- The RAT class creates an entry in the F10 menu which allows to +-- +-- * Create new groups on-the-fly, i.e. at run time within the mission, +-- * Destroy specific groups (e.g. if they get stuck or damaged and block a runway), +-- * Request the status of all RAT aircraft or individual groups, +-- * Place markers at waypoints on the F10 map for each group. +-- +-- Note that by its very nature, this class is suited best for civil or transport aircraft. However, it also works perfectly fine for military aircraft of any kind. +-- +-- More of the documentation include some simple examples can be found further down this page. +-- +-- ==== +-- +-- # Demo Missions +-- +-- ### [RAT Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/Release/RAT%20-%20Random%20Air%20Traffic) +-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) +-- +-- ==== +-- +-- # YouTube Channel +-- +-- ### RAT videos are work in progress. +-- ### [MOOSE YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) +-- +-- === +-- +-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** +-- +-- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))** +-- +-- ==== +-- @module Rat + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- RAT class +-- @type RAT +-- @field #string ClassName Name of the Class. +-- @field #boolean debug Turn debug messages on or off. +-- @field Core.Group#GROUP templategroup Group serving as template for the RAT aircraft. +-- @field #string alias Alias for spawned group. +-- @field #boolean spawninitialized If RAT:Spawn() was already called this RAT object is set to true to prevent users to call it again. +-- @field #number spawndelay Delay time in seconds before first spawning happens. +-- @field #number spawninterval Interval between spawning units/groups. Note that we add a randomization of 50%. +-- @field #number coalition Coalition of spawn group template. +-- @field #number country Country of spawn group template. +-- @field #string category Category of aircarft: "plane" or "heli". +-- @field #string friendly Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. +-- @field #table ctable Table with the valid coalitons from choice self.friendly. +-- @field #table aircraft Table which holds the basic aircraft properties (speed, range, ...). +-- @field #number Vcruisemax Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user. +-- @field #number Vclimb Default climb rate in ft/min. +-- @field #number AlphaDescent Default angle of descenti in degrees. A value of 3.6 follows the 3:1 rule of 3 miles of travel and 1000 ft descent. +-- @field #string roe ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free". +-- @field #string rot ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade". +-- @field #number takeoff Takeoff type. 0=coldorhot. +-- @field #number landing Landing type. Determines if we actually land at an airport or treat it as zone. +-- @field #number mindist Min distance from departure to destination in meters. Default 5 km. +-- @field #number maxdist Max distance from departure to destination in meters. Default 5000 km. +-- @field #table airports_map All airports available on current map (Caucasus, Nevada, Normandy, ...). +-- @field #table airports All airports of friedly coalitions. +-- @field #boolean random_departure By default a random friendly airport is chosen as departure. +-- @field #boolean random_destination By default a random friendly airport is chosen as destination. +-- @field #table departure_ports Array containing the names of the destination airports or zones. +-- @field #table destination_ports Array containing the names of the destination airports or zones. +-- @field #number Ndestination_Airports Number of destination airports set via SetDestination(). +-- @field #number Ndestination_Zones Number of destination zones set via SetDestination(). +-- @field #number Ndeparture_Airports Number of departure airports set via SetDeparture(). +-- @field #number Ndeparture_Zones Number of departure zones set via SetDeparture. +-- @field #table excluded_ports Array containing the names of explicitly excluded airports. +-- @field #boolean destinationzone Destination is a zone and not an airport. +-- @field #table return_zones Array containing the names of the return zones. +-- @field #boolean returnzone Zone where aircraft will fly to before returning to their departure airport. +-- @field Core.Zone#ZONE departure_Azone Zone containing the departure airports. +-- @field Core.Zone#ZONE destination_Azone Zone containing the destination airports. +-- @field #boolean addfriendlydepartures Add all friendly airports to departures. +-- @field #boolean addfriendlydestinations Add all friendly airports to destinations. +-- @field #table ratcraft Array with the spawned RAT aircraft. +-- @field #number Tinactive Time in seconds after which inactive units will be destroyed. Default is 300 seconds. +-- @field #boolean reportstatus Aircraft report status. +-- @field #number statusinterval Intervall between status checks (and reports if enabled). +-- @field #boolean placemarkers Place markers of waypoints on F10 map. +-- @field #number FLcruise Cruise altitude of aircraft. Default FL200 for planes and F005 for helos. +-- @field #number FLuser Flight level set by users explicitly. +-- @field #number FLminuser Minimum flight level set by user. +-- @field #number FLmaxuser Maximum flight level set by user. +-- @field #boolean commute Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation. +-- @field #boolean continuejourney Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. +-- @field #number ngroups Number of groups to be spawned in total. +-- @field #number alive Number of groups which are alive. +-- @field #boolean f10menu Add an F10 menu for RAT. +-- @field #table Menu F10 menu items for this RAT object. +-- @field #string SubMenuName Submenu name for RAT object. +-- @field #boolean respawn_at_landing Respawn aircraft the moment they land rather than at engine shutdown. +-- @field #boolean norespawn Aircraft will not be respawned after they have finished their route. +-- @field #boolean respawn_after_takeoff Aircraft will be respawned directly after take-off. +-- @field #number respawn_delay Delay in seconds until repawn happens after landing. +-- @field #table markerids Array with marker IDs. +-- @field #table waypointdescriptions Table with strings for waypoint descriptions of markers. +-- @field #table waypointstatus Table with strings of waypoint status. +-- @field #string livery Livery of the aircraft set by user. +-- @field #string skill Skill of AI. +-- @field #boolean ATCswitch Enable/disable ATC if set to true/false. +-- @field #string parking_id String with a special parking ID for the aircraft. +-- @field #boolean radio If true/false disables radio messages from the RAT groups. +-- @field #number frequency Radio frequency used by the RAT groups. +-- @field #string modulation Ratio modulation. Either "FM" or "AM". +-- @field #boolean uncontrolled If true aircraft are spawned in uncontrolled state and will only sit on their parking spots. +-- @extends Core.Spawn#SPAWN + +---# RAT class, extends @{Spawn#SPAWN} +-- The RAT class implements an easy to use way to randomly fill your map with AI aircraft. +-- +-- +-- ## Airport Selection +-- +-- ![Process](..\Presentations\RAT\RAT_Airport_Selection.png) +-- +-- ### Default settings: +-- +-- * By default, aircraft are spawned at airports of their own coalition (blue or red) or neutral airports. +-- * Destination airports are by default also of neutral or of the same coalition as the template group of the spawned aircraft. +-- * Possible destinations are restricted by their distance to the departure airport. The maximal distance depends on the max range of spawned aircraft type and its initial fuel amount. +-- +-- ### The default behavior can be changed: +-- +-- * A specific departure and/or destination airport can be chosen. +-- * Valid coalitions can be set, e.g. only red, blue or neutral, all three "colours". +-- * It is possible to start in air within a zone defined in the mission editor or within a zone above an airport of the map. +-- +-- ## Flight Plan +-- +-- ![Process](..\Presentations\RAT\RAT_Flight_Plan.png) +-- +-- * A general flight plan has five main airborne segments: Climb, cruise, descent, holding and final approach. +-- * Events monitored during the flight are: birth, engine-start, take-off, landing and engine-shutdown. +-- * The default flight level (FL) is set to ~FL200, i.e. 20000 feet ASL but randomized for each aircraft. +-- Service ceiling of aircraft type is into account for max FL as well as the distance between departure and destination. +-- * Maximal distance between destination and departure airports depends on range and initial fuel of aircraft. +-- * Climb rate is set to a moderate value of ~1500 ft/min. +-- * The standard descent rate follows the 3:1 rule, i.e. 1000 ft decent per 3 miles of travel. Hence, angle of descent is ~3.6 degrees. +-- * A holding point is randomly selected at a distance between 5 and 10 km away from destination airport. +-- * The altitude of theholding point is ~1200 m AGL. Holding patterns might or might not happen with variable duration. +-- * If an aircraft is spawned in air, the procedure omitts taxi and take-off and starts with the climb/cruising part. +-- * All values are randomized for each spawned aircraft. +-- +-- ## Mission Editor Setup +-- +-- ![Process](..\Presentations\RAT\RAT_Mission_Setup.png) +-- +-- Basic mission setup is very simple and essentially a three step process: +-- +-- * Place your aircraft **anywhere** on the map. It really does not matter where you put it. +-- * Give the group a good name. In the example above the group is named "RAT_YAK". +-- * Activate the "LATE ACTIVATION" tick box. Note that this aircraft will not be spawned itself but serves a template for each RAT aircraft spawned when the mission starts. +-- +-- Voilà, your already done! +-- +-- Optionally, you can set a specific livery for the aircraft or give it some weapons. +-- However, the aircraft will by default not engage any enemies. Think of them as beeing on a peaceful or ferry mission. +-- +-- ## Basic Lua Script +-- +-- ![Process](..\Presentations\RAT\RAT_Basic_Lua_Script.png) +-- +-- The basic Lua script for one template group consits of two simple lines as shown in the picture above. +-- +-- * **Line 2** creates a new RAT object "yak". The only required parameter for the constructor @{#RAT.New}() is the name of the group as defined in the mission editor. In this example it is "RAT_YAK". +-- * **Line 5** trigger the command to spawn the aircraft. The (optional) parameter for the @{#RAT.Spawn}() function is the number of aircraft to be spawned of this object. +-- By default each of these aircraft gets a random departure airport anywhere on the map and a random destination airport, which lies within range of the of the selected aircraft type. +-- +-- In this simple example aircraft are respawned with a completely new flightplan when they have reached their destination airport. +-- The "old" aircraft is despawned (destroyed) after it has shut-down its engines and a new aircraft of the same type is spawned at a random departure airport anywhere on the map. +-- Hence, the default flight plan for a RAT aircraft will be: Fly from airport A to B, get respawned at C and fly to D, get respawned at E and fly to F, ... +-- This ensures that you always have a constant number of AI aircraft on your map. +-- +-- ## Examples +-- +-- Here are a few examples, how you can modify the default settings of RAT class objects. +-- +-- ### Specify Departure and Destinations +-- +-- ![Process](..\Presentations\RAT\RAT_Examples_Specify_Departure_and_Destination.png) +-- +-- In the picture above you find a few possibilities how to modify the default behaviour to spawn at random airports and fly to random destinations. +-- +-- In particular, you can specify fixed departure and/or destination airports. This is done via the @{#RAT.SetDeparture}() or @{#RAT.SetDestination}() functions, respectively. +-- +-- * If you only fix a specific departure airport via @{#RAT.SetDeparture}() all aircraft will be spawned at that airport and get random destination airports. +-- * If you only fix the destination airport via @{#RAT.SetDestination}(), aircraft a spawned at random departure airports but will all fly to the destination airport. +-- * If you fix departure and destination airports, aircraft will only travel from between those airports. +-- When the aircraft reaches its destination, it will be respawned at its departure and fly again to its destination. +-- +-- There is also an option that allows aircraft to "continue their journey" from their destination. This is achieved by the @{#RAT.ContinueJourney}() function. +-- In that case, when the aircraft arrives at its first destination it will be respawned at that very airport and get a new random destination. +-- So the flight plan in this case would be: Fly from airport A to B, then from B to C, then from C to D, ... +-- +-- It is also possible to make aircraft "commute" between two airports, i.e. flying from airport A to B and then back from B to A, etc. +-- This can be done by the @{#RAT.Commute}() function. Note that if no departure or destination airports are specified, the first departure and destination are chosen randomly. +-- Then the aircraft will fly back and forth between those two airports indefinetly. +-- +-- +-- ### Spawn in Air +-- +-- ![Process](..\Presentations\RAT\RAT_Examples_Spawn_in_Air.png) +-- +-- Aircraft can also be spawned in air rather than at airports on the ground. This is done by setting @{#RAT.SetTakeoff}() to "air". +-- +-- By default, aircraft are spawned randomly above airports of the map. +-- +-- The @{#RAT.SetDeparture}() option can be used to specify zones, which have been defined in the mission editor as departure zones. +-- Aircraft will then be spawned at a random point within the zone or zones. +-- +-- Note that @{#RAT.SetDeparture}() also accepts airport names. For an air takeoff these are treated like zones with a radius of XX kilometers. +-- Again, aircraft are spawned at random points within these zones around the airport. +-- +-- ### Misc Options +-- +-- ![Process](..\Presentations\RAT\RAT_Examples_Misc.png) +-- +-- The default "takeoff" type of RAT aircraft is that they are spawned with hot or cold engines. +-- The choice is random, so 50% of aircraft will be spawned with hot engines while the other 50% will be spawned with cold engines. +-- This setting can be changed using the @{#RAT.SetTakeoff}() function. The possible parameters for starting on ground are: +-- +-- * @{#RAT.SetTakeoff}("cold"), which means that all aircraft are spawned with their engines off, +-- * @{#RAT.SetTakeoff}("hot"), which means that all aircraft are spawned with their engines on, +-- * @{#RAT.SetTakeoff}("runway"), which means that all aircraft are spawned already at the runway ready to takeoff. +-- Note that in this case the default spawn intervall is set to 180 seconds in order to avoid aircraft jamms on the runway. Generally, this takeoff at runways should be used with care and problems are to be expected. +-- +-- +-- The options @{#RAT.SetMinDistance}() and @{#RAT.SetMaxDistance}() can be used to restrict the range from departure to destination. For example +-- +-- * @{#RAT.SetMinDistance}(100) will cause only random destination airports to be selected which are **at least** 100 km away from the departure airport. +-- * @{#RAT.SetMaxDistance}(150) will allow only destination airports which are **less than** 150 km away from the departure airport. +-- +-- ![Process](..\Presentations\RAT\RAT_Gaussian.png) +-- +-- By default planes get a cruise altitude of ~20,000 ft ASL. The actual altitude is sampled from a Gaussian distribution. The picture shows this distribution +-- if one would spawn 1000 planes. As can be seen most planes get a cruising alt of around FL200. Other values are possible but less likely the further away +-- one gets from the expectation value. +-- +-- The expectation value, i.e. the altitude most aircraft get, can be set with the function @{#RAT.SetFLcruise}(). +-- It is possible to restrict the minimum cruise altitude by @{#RAT.SetFLmin}() and the maximum cruise altitude by @{#RAT.SetFLmax}() +-- +-- The cruise altitude can also be given in meters ASL by the functions @{#RAT.SetCruiseAltitude}(), @{#RAT.SetMinCruiseAltitude}() and @{#RAT.SetMaxCruiseAltitude}(). +-- +-- For example: +-- +-- * @{#RAT.SetFLcruise}(300) will cause most planes fly around FL300. +-- * @{#RAT.SetFLmin}(100) restricts the cruising alt such that no plane will fly below FL100. Note that this automatically changes the minimum distance from departure to destination. +-- That means that only destinations are possible for which the aircraft has had enought time to reach that flight level and descent again. +-- * @{#RAT.SetFLmax}(200) will restrict the cruise alt to maximum FL200, i.e. no aircraft will travel above this height. +-- +-- +-- @field #RAT +RAT={ + ClassName = "RAT", -- Name of class: RAT = Random Air Traffic. + debug=false, -- Turn debug messages on or off. + templategroup=nil, -- Template group for the RAT aircraft. + alias=nil, -- Alias for spawned group. + spawninitialized=false, -- If RAT:Spawn() was already called this is set to true to prevent users to call it again. + spawndelay=5, -- Delay time in seconds before first spawning happens. + spawninterval=5, -- Interval between spawning units/groups. Note that we add a randomization of 50%. + coalition = nil, -- Coalition of spawn group template. + country = nil, -- Country of the group template. + category = nil, -- Category of aircarft: "plane" or "heli". + friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral. + ctable = {}, -- Table with the valid coalitons from choice self.friendly. + aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...). + Vcruisemax=nil, -- Max cruise speed in set by user. + Vclimb=1500, -- Default climb rate in ft/min. + AlphaDescent=3.6, -- Default angle of descenti in degrees. A value of 3.6 follows the 3:1 rule of 3 miles of travel and 1000 ft descent. + roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free". + rot = "noreaction", -- ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade". + takeoff = 0, -- Takeoff type. 0=coldorhot. + landing = 9, -- Landing type. 9=landing. + mindist = 5000, -- Min distance from departure to destination in meters. Default 5 km. + maxdist = 5000000, -- Max distance from departure to destination in meters. Default 5000 km. + airports_map={}, -- All airports available on current map (Caucasus, Nevada, Normandy, ...). + airports={}, -- All airports of friedly coalitions. + random_departure=true, -- By default a random friendly airport is chosen as departure. + random_destination=true, -- By default a random friendly airport is chosen as destination. + departure_ports={}, -- Array containing the names of the departure airports or zones. + destination_ports={}, -- Array containing the names of the destination airports or zones. + Ndestination_Airports=0, -- Number of destination airports set via SetDestination(). + Ndestination_Zones=0, -- Number of destination zones set via SetDestination(). + Ndeparture_Airports=0, -- Number of departure airports set via SetDeparture(). + Ndeparture_Zones=0, -- Number of departure zones set via SetDeparture. + destinationzone=false, -- Destination is a zone and not an airport. + return_zones={}, -- Array containing the names of return zones. + returnzone=false, -- Aircraft will fly to a zone and back. + excluded_ports={}, -- Array containing the names of explicitly excluded airports. + departure_Azone=nil, -- Zone containing the departure airports. + destination_Azone=nil, -- Zone containing the destination airports. + addfriendlydepartures=false, -- Add all friendly airports to departures. + addfriendlydestinations=false, -- Add all friendly airports to destinations. + ratcraft={}, -- Array with the spawned RAT aircraft. + Tinactive=600, -- Time in seconds after which inactive units will be destroyed. Default is 600 seconds. + reportstatus=false, -- Aircraft report status. + statusinterval=30, -- Intervall between status checks (and reports if enabled). + placemarkers=false, -- Place markers of waypoints on F10 map. + FLcruise=nil, -- Cruise altitude of aircraft. Default FL200 for planes and F005 for helos. + FLminuser=nil, -- Minimum flight level set by user. + FLmaxuser=nil, -- Maximum flight level set by user. + FLuser=nil, -- Flight level set by users explicitly. + commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation. + continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. + alive=0, -- Number of groups which are alive. + ngroups=nil, -- Number of groups to be spawned in total. + f10menu=true, -- Add an F10 menu for RAT. + Menu={}, -- F10 menu items for this RAT object. + SubMenuName=nil, -- Submenu name for RAT object. + respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown. + norespawn=false, -- Aircraft will not get respawned. + respawn_after_takeoff=false, -- Aircraft will be respawned directly after takeoff. + respawn_delay=nil, -- Delay in seconds until repawn happens after landing. + markerids={}, -- Array with marker IDs. + waypointdescriptions={}, -- Array with descriptions for waypoint markers. + waypointstatus={}, -- Array with status info on waypoints. + livery=nil, -- Livery of the aircraft. + skill="High", -- Skill of AI. + ATCswitch=true, -- Enable ATC. + parking_id=nil, -- Specific parking ID when aircraft are spawned at airports. + radio=nil, -- If true/false disables radio messages from the RAT groups. + frequency=nil, -- Radio frequency used by the RAT groups. + modulation=nil, -- Ratio modulation. Either "FM" or "AM". + actype=nil, -- Aircraft type set by user. Changes the type of the template group. + uncontrolled=false, -- Spawn uncontrolled aircraft. +} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Categories of the RAT class. +-- @list cat +-- @field #string plane Plane. +-- @field #string heli Heli. +RAT.cat={ + plane="plane", + heli="heli", +} + +--- RAT waypoint type. +-- @list wp +RAT.wp={ + coldorhot=0, + air=1, + runway=2, + hot=3, + cold=4, + climb=5, + cruise=6, + descent=7, + holding=8, + landing=9, + finalwp=10, +} + +--- RAT aircraft status. +-- @list status +RAT.status={ + -- Waypoint states. + Departure="At departure point", + Climb="Climbing", + Cruise="Cruising", + Uturn="Flying back home", + Descent="Descending", + DescentHolding="Descend to holding point", + Holding="Holding", + Destination="Arrived at destination", + -- Event states. + EventBirthAir="Born in air", + EventBirth="Ready and starting engines", + EventEngineStartAir="On journey", -- Started engines (in air) + EventEngineStart="Started engines and taxiing", + EventTakeoff="Airborne after take-off", + EventLand="Landed and taxiing", + EventEngineShutdown="Engines off", + EventDead="Dead", + EventCrash="Crashed", +} + +--- RAT friendly coalitions. +-- @list coal +RAT.coal={ + same="same", + sameonly="sameonly", + neutral="neutral", +} + +--- RAT unit conversions. +-- @list unit +RAT.unit={ + ft2meter=0.305, + kmh2ms=0.278, + FL2m=30.48, + nm2km=1.852, + nm2m=1852, +} + +--- RAT rules of engagement. +-- @list ROE +RAT.ROE={ + weaponhold="hold", + weaponfree="free", + returnfire="return", +} + +--- RAT reaction to threat. +-- @list ROT +RAT.ROT={ + evade="evade", + passive="passive", + noreaction="noreaction", +} + +RAT.ATC={ + init=false, + flight={}, + airport={}, + unregistered=-1, + onfinal=-100, + Nclearance=2, + delay=240, +} + +--- Running number of placed markers on the F10 map. +-- @field #number markerid +RAT.markerid=0 + +--- Main F10 menu. +-- @field #string MenuF10 +RAT.MenuF10=nil + +--- Some ID to identify who we are in output of the DCS.log file. +-- @field #string id +RAT.id="RAT | " + +--- RAT version. +-- @field #string version +RAT.version="2.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--TODO list: +--DONE: Add scheduled spawn. +--DONE: Add possibility to spawn in air. +--DONE: Add departure zones for air start. +--DONE: Make more functions to adjust/set RAT parameters. +--DONE: Clean up debug messages. +--DONE: Improve flight plan. Especially check FL against route length. +--DONE: Add event handlers. +--DONE: Respawn units when they have landed. +--DONE: Change ROE state. +--DONE: Make ROE state user function +--DONE: Improve status reports. +--DONE: Check compatibility with other #SPAWN functions. nope, not all! +--DONE: Add possibility to continue journey at destination. Need "place" in event data for that. +--DONE: Add enumerators and get rid off error prone string comparisons. +--DONE: Check that FARPS are not used as airbases for planes. +--DONE: Add special cases for ships (similar to FARPs). +--DONE: Add cases for helicopters. +--DONE: Add F10 menu. +--DONE: Add markers to F10 menu. +--DONE: Add respawn limit. Later... +--DONE: Make takeoff method random between cold and hot start. +--DONE: Check out uncontrolled spawning. Not now! +--DONE: Check aircraft spawning in air at Sochi after third aircraft was spawned. ==> DCS behaviour. +--DONE: Improve despawn after stationary. Might lead to despawning if many aircraft spawn at the same time. +--DONE: Check why birth event is not handled. ==> Seems to be okay if it is called _OnBirth rather than _OnBirthday. Dont know why actually!? +--DONE: Improve behaviour when no destination or departure airports were found. Leads to crash, e.g. 1184: attempt to get length of local 'destinations' (a nil value) +--DONE: Check cases where aircraft get shot down. +--DONE: Handle the case where more than 10 RAT objects are spawned. Likewise, more than 10 groups of one object. Causes problems with the number of menu items! ==> not now! +--DONE: Add custom livery choice if possible. +--TODO: When only a destination is set, it should be checked that the departure is within range. Also, that departure and destination are not the same. +--TODO: Add function to include all airports to selected destinations/departures. +--DONE: Find way to respawn aircraft at same position where the last was despawned for commute and journey. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new RAT object. +-- @param #RAT self +-- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units. +-- @param #string alias (Optional) Alias of the group. This is and optional parameter but must(!) be used if the same template group is used for more than one RAT object. +-- @return #RAT Object of RAT class. +-- @return #nil If the group does not exist in the mission editor. +-- @usage yak1:RAT("RAT_YAK") will create a RAT object called "yak1". The template group in the mission editor must have the name "RAT_YAK". +-- @usage yak2:RAT("RAT_YAK", "Yak2") will create a RAT object "yak2". The template group in the mission editor must have the name "RAT_YAK" but the group will be called "Yak2" in e.g. the F10 menu. +function RAT:New(groupname, alias) + + -- Version info. + env.info(RAT.id.."Version "..RAT.version) + + -- Welcome message. + env.info(RAT.id.."Creating new RAT object from template: "..groupname) + + -- Set alias. + alias=alias or groupname + + -- Inherit SPAWN class. + local self=BASE:Inherit(self, SPAWN:NewWithAlias(groupname, alias)) -- #RAT + + -- Alias of groupname. + self.alias=alias + + -- Get template group defined in the mission editor. + local DCSgroup=Group.getByName(groupname) + + -- Check the group actually exists. + if DCSgroup==nil then + env.error(RAT.id.."Group with name "..groupname.." does not exist in the mission editor!") + return nil + end + + -- Store template group. + self.templategroup=GROUP:FindByName(groupname) + + -- Set own coalition. + self.coalition=DCSgroup:getCoalition() + + -- Initialize aircraft parameters based on ME group template. + self:_InitAircraft(DCSgroup) + + -- Get all airports of current map (Caucasus, NTTR, Normandy, ...). + self:_GetAirportsOfMap() + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Triggers the spawning of AI aircraft. Note that all additional options should be set before giving the spawn command. +-- @param #RAT self +-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. +-- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton. +function RAT:Spawn(naircraft) + + -- Make sure that this function is only been called once per RAT object. + if self.spawninitialized==true then + env.error("Spawn function should only be called once per RAT object! Exiting and returning nil.") + return nil + else + self.spawninitialized=true + end + + -- Number of aircraft to spawn. Default is one. + self.ngroups=naircraft or 1 + + -- Init RAT ATC if not already done. + if self.ATCswitch and not RAT.ATC.init then + RAT:_ATCInit(self.airports_map) + end + + -- Create F10 main menu if it does not exists yet. + if self.f10menu and not RAT.MenuF10 then + RAT.MenuF10 = MENU_MISSION:New("RAT") + end + + -- Set the coalition table based on choice of self.coalition and self.friendly. + self:_SetCoalitionTable() + + -- Get all airports of this map beloning to friendly coalition(s). + self:_GetAirportsOfCoalition() + + -- Set submenuname if it has not been set by user. + if not self.SubMenuName then + self.SubMenuName=self.alias + end + + -- Get all departure airports inside a Moose zone. + if self.departure_Azone~=nil then + self.departure_ports=self:_GetAirportsInZone(self.departure_Azone) + end + + -- Get all destination airports inside a Moose zone. + if self.destination_Azone~=nil then + self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) + end + + -- Add all friendly airports to possible departures/destinations + if self.addfriendlydepartures then + self:_AddFriendlyAirports(self.departure_ports) + end + if self.addfriendlydestinations then + self:_AddFriendlyAirports(self.destination_ports) + end + + -- Setting and possibly correction min/max/cruise flight levels. + if self.FLcruise==nil then + -- Default flight level (ASL). + if self.category==RAT.cat.plane then + -- For planes: FL200 = 20000 ft = 6096 m. + self.FLcruise=200*RAT.unit.FL2m + else + -- For helos: FL005 = 500 ft = 152 m. + self.FLcruise=005*RAT.unit.FL2m + end + end + + -- Run consistency checks. + self:_CheckConsistency() + + -- Settings info + local text=string.format("\n******************************************************\n") + text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", self.ngroups, self.SpawnTemplatePrefix, self.aircraft.type) + text=text..string.format("Alias: %s\n", self.alias) + text=text..string.format("Category: %s\n", self.category) + text=text..string.format("Friendly coalitions: %s\n", self.friendly) + text=text..string.format("Number of airports on map : %i\n", #self.airports_map) + text=text..string.format("Number of friendly airports: %i\n", #self.airports) + text=text..string.format("Totally random departure: %s\n", tostring(self.random_departure)) + if not self.random_departure then + text=text..string.format("Number of departure airports: %d\n", self.Ndeparture_Airports) + text=text..string.format("Number of departure zones : %d\n", self.Ndeparture_Zones) + end + text=text..string.format("Totally random destination: %s\n", tostring(self.random_destination)) + if not self.random_destination then + text=text..string.format("Number of destination airports: %d\n", self.Ndestination_Airports) + text=text..string.format("Number of destination zones : %d\n", self.Ndestination_Zones) + end + text=text..string.format("Min dist to destination: %4.1f\n", self.mindist) + text=text..string.format("Max dist to destination: %4.1f\n", self.maxdist) + text=text..string.format("Takeoff type: %i\n", self.takeoff) + text=text..string.format("Landing type: %i\n", self.landing) + text=text..string.format("Commute: %s\n", tostring(self.commute)) + text=text..string.format("Journey: %s\n", tostring(self.continuejourney)) + text=text..string.format("Destination Zone: %s\n", tostring(self.destinationzone)) + text=text..string.format("Return Zone: %s\n", tostring(self.returnzone)) + text=text..string.format("Uncontrolled: %s\n", tostring(self.uncontrolled)) + text=text..string.format("Spawn delay: %4.1f\n", self.spawndelay) + text=text..string.format("Spawn interval: %4.1f\n", self.spawninterval) + text=text..string.format("Respawn after landing: %s\n", tostring(self.respawn_at_landing)) + text=text..string.format("Respawning off: %s\n", tostring(self.norespawn)) + text=text..string.format("Respawn after take-off: %s\n", tostring(self.respawn_after_takeoff)) + text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay)) + text=text..string.format("ROE: %s\n", tostring(self.roe)) + text=text..string.format("ROT: %s\n", tostring(self.rot)) + text=text..string.format("Vclimb: %4.1f\n", self.Vclimb) + text=text..string.format("AlphaDescent: %4.2f\n", self.AlphaDescent) + text=text..string.format("Vcruisemax: %s\n", tostring(self.Vcruisemax)) + text=text..string.format("FLcruise = %6.1f km = FL%3.0f\n", self.FLcruise/1000, self.FLcruise/RAT.unit.FL2m) + text=text..string.format("FLuser: %s\n", tostring(self.Fluser)) + text=text..string.format("FLminuser: %s\n", tostring(self.FLminuser)) + text=text..string.format("FLmaxuser: %s\n", tostring(self.FLmaxuser)) + text=text..string.format("Place markers: %s\n", tostring(self.placemarkers)) + text=text..string.format("Report status: %s\n", tostring(self.reportstatus)) + text=text..string.format("Status interval: %4.1f\n", self.statusinterval) + text=text..string.format("Time inactive: %4.1f\n", self.Tinactive) + text=text..string.format("Create F10 menu : %s\n", tostring(self.f10menu)) + text=text..string.format("F10 submenu name: %s\n", self.SubMenuName) + text=text..string.format("ATC enabled : %s\n", tostring(self.ATCswitch)) + text=text..string.format("Radio comms : %s\n", tostring(self.radio)) + text=text..string.format("Radio frequency : %s\n", tostring(self.frequency)) + text=text..string.format("Radio modulation : %s\n", tostring(self.frequency)) + if self.livery then + text=text..string.format("Available liveries:\n") + for _,livery in pairs(self.livery) do + text=text..string.format("- %s\n", livery) + end + end + text=text..string.format("******************************************************\n") + env.info(RAT.id..text) + + -- Create submenus. + if self.f10menu then + self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName, RAT.MenuF10) + self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups", self.Menu[self.SubMenuName]) + MENU_MISSION_COMMAND:New("Spawn new group", self.Menu[self.SubMenuName], self._SpawnWithRoute, self) + MENU_MISSION_COMMAND:New("Delete markers", self.Menu[self.SubMenuName], self._DeleteMarkers, self) + MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName], self.Status, self, true) + end + + -- Schedule spawning of aircraft. + local Tstart=self.spawndelay + local dt=self.spawninterval + -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed. + if self.takeoff==RAT.wp.runway and not self.random_departure then + dt=math.max(dt, 180) + end + local Tstop=Tstart+dt*(self.ngroups-1) + SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.0, Tstop) + + -- Status check and report scheduler. + SCHEDULER:New(nil, self.Status, {self}, Tstart+1, self.statusinterval) + + -- Handle events. + self:HandleEvent(EVENTS.Birth, self._OnBirth) + self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup) + self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff) + self:HandleEvent(EVENTS.Land, self._OnLand) + self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown) + self:HandleEvent(EVENTS.Dead, self._OnDead) + self:HandleEvent(EVENTS.Crash, self._OnCrash) + -- TODO: add hit event? + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function checks consistency of user input and automatically adjusts parameters if necessary. +-- @param #RAT self +function RAT:_CheckConsistency() + + -- User has used SetDeparture() + if not self.random_departure then + + -- Count departure airports and zones. + for _,name in pairs(self.departure_ports) do + if self:_AirportExists(name) then + self.Ndeparture_Airports=self.Ndeparture_Airports+1 + elseif self:_ZoneExists(name) then + self.Ndeparture_Zones=self.Ndeparture_Zones+1 + end + end + + -- What can go wrong? + -- Only zones but not takeoff air == > Enable takeoff air. + if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then + self.takeoff=RAT.wp.air + env.error(RAT.id.."At least one zone defined as departure and takeoff is NOT set to air. Enabling air start!") + end + -- No airport and no zone specified. + if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then + self.random_departure=true + local text="No airports or zones found given in SetDeparture(). Enabling random departure airports!" + env.error(RAT.id..text) + MESSAGE:New(text, 30):ToAll() + end + end + + -- User has used SetDestination() + if not self.random_destination then + + -- Count destination airports and zones. + for _,name in pairs(self.destination_ports) do + if self:_AirportExists(name) then + self.Ndestination_Airports=self.Ndestination_Airports+1 + elseif self:_ZoneExists(name) then + self.Ndestination_Zones=self.Ndestination_Zones+1 + end + end + + -- One zone specified as destination ==> Enable destination zone. + -- This does not apply to return zone because the destination is the zone and not the final destination which can be an airport. + if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then + self.landing=RAT.wp.air + self.destinationzone=true + env.error(RAT.id.."At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!") + end + -- No specified airport and no zone found at all. + if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then + self.random_destination=true + local text="No airports or zones found given in SetDestination(). Enabling random destination airports!" + env.error(RAT.id..text) + MESSAGE:New(text, 30):ToAll() + end + end + + -- Destination zone and return zone should not be used together. + if self.destinationzone and self.returnzone then + env.error(RAT.id.."Destination zone _and_ return to zone not possible! Disabling return to zone.") + self.returnzone=false + end + -- If returning to a zone, we set the landing type to "air" if takeoff is in air. + -- Because if we start in air we want to end in air. But default landing is ground. + if self.returnzone and self.takeoff==RAT.wp.air then + self.landing=RAT.wp.air + end + + -- Ensure that neither FLmin nor FLmax are above the aircrafts service ceiling. + if self.FLminuser then + self.FLminuser=math.min(self.FLminuser, self.aircraft.ceiling) + end + if self.FLmaxuser then + self.FLmaxuser=math.min(self.FLmaxuser, self.aircraft.ceiling) + end + if self.FLcruise then + self.FLcruise=math.min(self.FLcruise, self.aircraft.ceiling) + end + + -- FL min > FL max case ==> spaw values + if self.FLminuser and self.FLmaxuser then + if self.FLminuser > self.FLmaxuser then + local min=self.FLminuser + local max=self.FLmaxuser + self.FLminuser=max + self.FLmaxuser=min + end + end + + -- Cruise alt < FL min + if self.FLminuser and self.FLcruise FL max + if self.FLmaxuser and self.FLcruise>self.FLmaxuser then + self.FLcruise=self.FLmaxuser + end + + -- Uncontrolled aircraft must start with engines off. + if self.uncontrolled then + -- TODO: Strangly, it does not work with RAT.wp.cold only with RAT.wp.hot! + self.takeoff=RAT.wp.hot + end +end + +--- Set the friendly coalitions from which the airports can be used as departure and destination. +-- @param #RAT self +-- @param #string friendly "same"=own coalition+neutral (default), "sameonly"=own coalition only, "neutral"=all neutral airports. +-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports. +-- @usage yak:SetCoalition("neutral") will spawn aircraft randomly on all neutral airports. +-- @usage yak:SetCoalition("sameonly") will spawn aircraft randomly on airports belonging to the same coalition only as the template. +function RAT:SetCoalition(friendly) + if friendly:lower()=="sameonly" then + self.friendly=RAT.coal.sameonly + elseif friendly:lower()=="neutral" then + self.friendly=RAT.coal.neutral + else + self.friendly=RAT.coal.same + end +end + +--- Set coalition of RAT group. You can make red templates blue and vice versa. +-- @param #RAT self +-- @param #string color Color of coalition, i.e. "red" or blue". +function RAT:SetCoalitionAircraft(color) + if color:lower()=="blue" then + self.coalition=coalition.side.BLUE + if not self.country then + self.country=country.id.USA + end + elseif color:lower()=="red" then + self.coalition=coalition.side.RED + if not self.country then + self.country=country.id.RUSSIA + end + elseif color:lower()=="neutral" then + self.coalition=coalition.side.NEUTRAL + end +end + +--- Set country of RAT group. This overrules the coalition settings. +-- @param #RAT self +-- @param #number id DCS country enumerator ID. For example country.id.USA or country.id.RUSSIA. +function RAT:SetCountry(id) + self.country=id +end + +--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air. +-- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines. +-- @param #RAT self +-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air". +-- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started. +-- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off. +-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. +function RAT:SetTakeoff(type) + + local _Type + if type:lower()=="takeoff-cold" or type:lower()=="cold" then + _Type=RAT.wp.cold + elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then + _Type=RAT.wp.hot + elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then + _Type=RAT.wp.runway + elseif type:lower()=="air" then + _Type=RAT.wp.air + else + _Type=RAT.wp.coldorhot + end + + self.takeoff=_Type +end + +--- Set possible departure ports. This can be an airport or a zone defined in the mission editor. +-- @param #RAT self +-- @param #string departurenames Name or table of names of departure airports or zones. +-- @usage RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport. +-- @usage RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport. +-- @usage RAT:SetDeparture({"Zone A", "Gudauta"}) will spawn RAT aircraft in air randomly within Zone A, which has to be defined in the mission editor, or within a zone around Gudauta airport. Note that this also requires RAT:takeoff("air") to be set. +function RAT:SetDeparture(departurenames) + + -- Random departure is deactivated now that user specified departure ports. + self.random_departure=false + + -- Convert input to table. + local names + if type(departurenames)=="table" then + names=departurenames + elseif type(departurenames)=="string" then + names={departurenames} + else + -- error message + env.error(RAT.id.."Input parameter must be a string or a table in SetDeparture()!") + end + + -- Put names into arrays. + for _,name in pairs(names) do + + if self:_AirportExists(name) then + -- If an airport with this name exists, we put it in the ports array. + table.insert(self.departure_ports, name) + elseif self:_ZoneExists(name) then + -- If it is not an airport, we assume it is a zone. + table.insert(self.departure_ports, name) + else + env.error(RAT.id.."ERROR! No departure airport or zone found with name "..name) + end + + end + +end + +--- Set name of destination airports or zones for the AI aircraft. +-- @param #RAT self +-- @param #string destinationnames Name of the destination airport or table of destination airports. +-- @usage RAT:SetDestination("Krymsk") makes all aircraft of this RAT oject fly to Krymsk airport. +function RAT:SetDestination(destinationnames) + + -- Random departure is deactivated now that user specified departure ports. + self.random_destination=false + + -- Convert input to table + local names + if type(destinationnames)=="table" then + names=destinationnames + elseif type(destinationnames)=="string" then + names={destinationnames} + else + -- Error message. + env.error(RAT.id.."Input parameter must be a string or a table in SetDestination()!") + end + + -- Put names into arrays. + for _,name in pairs(names) do + + if self:_AirportExists(name) then + -- If an airport with this name exists, we put it in the ports array. + table.insert(self.destination_ports, name) + elseif self:_ZoneExists(name) then + -- If it is not an airport, we assume it is a zone. + table.insert(self.destination_ports, name) + else + env.error(RAT.id.."ERROR! No destination airport or zone found with name "..name) + end + + end + +end + +--- Destinations are treated as zones. Aircraft will not land but rather be despawned when they reach a random point in the zone. +-- @param #RAT self +function RAT:DestinationZone() + -- Destination is a zone. Needs special care. + self.destinationzone=true + + -- Landing type is "air" because we don't actually land at the airport. + self.landing=RAT.wp.air +end + +--- Aircraft will fly to a random point within a zone and then return to its departure airport or zone. +-- @param #RAT self +function RAT:ReturnZone() + -- Destination is a zone. Needs special care. + self.returnzone=true +end + + +--- Include all airports which lie in a zone as possible destinations. +-- @param #RAT self +-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. +function RAT:SetDestinationsFromZone(zone) + + -- Random departure is deactivated now that user specified departure ports. + self.random_destination=false + + -- Set zone. + self.destination_Azone=zone +end + +--- Include all airports which lie in a zone as possible destinations. +-- @param #RAT self +-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. +function RAT:SetDeparturesFromZone(zone) + -- Random departure is deactivated now that user specified departure ports. + self.random_departure=false + + -- Set zone. + self.departure_Azone=zone +end + +--- Add all friendly airports to the list of possible departures. +-- @param #RAT self +function RAT:AddFriendlyAirportsToDepartures() + self.addfriendlydepartures=true +end + +--- Add all friendly airports to the list of possible destinations +-- @param #RAT self +function RAT:AddFriendlyAirportsToDestinations() + self.addfriendlydestinations=true +end + +--- Airports, FARPs and ships explicitly excluded as departures and destinations. +-- @param #RAT self +-- @param #string ports Name or table of names of excluded airports. +function RAT:ExcludedAirports(ports) + if type(ports)=="string" then + self.excluded_ports={ports} + else + self.excluded_ports=ports + end +end + +--- Set skill of AI aircraft. Default is "High". +-- @param #RAT self +-- @param #string skill Skill, options are "Average", "Good", "High", "Excellent" and "Random". Parameter is case insensitive. +function RAT:SetAISkill(skill) + if skill:lower()=="average" then + self.skill="Average" + elseif skill:lower()=="good" then + self.skill="Good" + elseif skill:lower()=="excellent" then + self.skill="Excellent" + elseif skill:lower()=="random" then + self.skill="Random" + else + self.skill="High" + end +end + +--- Set livery of aircraft. If more than one livery is specified in a table, the actually used one is chosen randomly from the selection. +-- @param #RAT self +-- @param #table skins Name of livery or table of names of liveries. +function RAT:Livery(skins) + if type(skins)=="string" then + self.livery={skins} + else + self.livery=skins + end +end + +--- Change aircraft type. This is a dirty hack which allows to change the aircraft type of the template group. +-- Note that all parameters like cruise speed, climb rate, range etc are still taken from the template group which likely leads to strange behaviour. +-- @param #RAT self +-- @param #string actype Type of aircraft which is spawned independent of the template group. Use with care and expect problems! +function RAT:ChangeAircraft(actype) + self.actype=actype +end + +--- Aircraft will continue their journey from their destination. This means they are respawned at their destination and get a new random destination. +-- @param #RAT self +function RAT:ContinueJourney() + self.continuejourney=true + self.commute=false +end + +--- Aircraft will commute between their departure and destination airports or zones. +-- @param #RAT self +function RAT:Commute() + self.commute=true + self.continuejourney=false +end + +--- Set the delay before first group is spawned. +-- @param #RAT self +-- @param #number delay Delay in seconds. Default is 5 seconds. Minimum delay is 0.5 seconds. +function RAT:SetSpawnDelay(delay) + delay=delay or 5 + self.spawndelay=math.max(0.5, delay) +end + +--- Set the interval between spawnings of the template group. +-- @param #RAT self +-- @param #number interval Interval in seconds. Default is 5 seconds. Minimum is 0.5 seconds. +function RAT:SetSpawnInterval(interval) + interval=interval or 5 + self.spawninterval=math.max(0.5, interval) +end + +--- Make aircraft respawn the moment they land rather than at engine shut down. +-- @param #RAT self +-- @param #number delay (Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. Minimum is 0.5 seconds. +function RAT:RespawnAfterLanding(delay) + delay = delay or 180 + self.respawn_at_landing=true + delay=math.max(0.5, delay) + self.respawn_delay=delay +end + +--- Aircraft will not get respawned when they finished their route. +-- @param #RAT self +function RAT:NoRespawn() + self.norespawn=true +end + +--- Aircraft will be respawned directly after take-off. +-- @param #RAT self +function RAT:RespawnAfterTakeoff() + self.respawn_after_takeoff=true +end + +--- Set parking id of aircraft. +-- @param #RAT self +-- @param #string id Parking ID of the aircraft. +function RAT:SetParkingID(id) + self.parking_id=id + env.info(RAT.id.."Setting parking ID to "..self.parking_id) +end + +--- Enable Radio. Overrules the ME setting. +-- @param #RAT self +function RAT:RadioON() + self.radio=true +end + +--- Disable Radio. Overrules the ME setting. +-- @param #RAT self +function RAT:RadioOFF() + self.radio=false +end + +--- Set radio frequency. +-- @param #RAT self +-- @param #number frequency Radio frequency. +function RAT:RadioFrequency(frequency) + self.frequency=frequency +end + +--- Spawn aircraft in uncontolled state. Aircraft will only sit at their parking spots. Only for populating airfields. +-- @param #RAT self +function RAT:Uncontrolled() + self.uncontrolled=true +end + +--- Set radio modulation. Default is AM. +-- @param #RAT self +-- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. +function RAT:RadioModulation(modulation) + if modulation=="AM" then + self.modulation=radio.modulation.AM + elseif modulation=="FM" then + self.modulation=radio.modulation.FM + else + self.modulation=radio.modulation.AM + end +end + +--- Set the time after which inactive groups will be destroyed. +-- @param #RAT self +-- @param #number time Time in seconds. Default is 600 seconds = 10 minutes. Minimum is 60 seconds. +function RAT:TimeDestroyInactive(time) + time=time or self.Tinactive + time=math.max(time, 60) + self.Tinactive=time +end + +--- Set the maximum cruise speed of the aircraft. +-- @param #RAT self +-- @param #number speed Speed in km/h. +function RAT:SetMaxCruiseSpeed(speed) + -- Convert to m/s. + self.Vcruisemax=speed/3.6 +end + +--- Set the climb rate. This automatically sets the climb angle. +-- @param #RAT self +-- @param #number rate Climb rate in ft/min. Default is 1500 ft/min. Minimum is 100 ft/min. Maximum is 15,000 ft/min. +function RAT:SetClimbRate(rate) + rate=rate or self.Vclimb + rate=math.max(rate, 100) + rate=math.min(rate, 15000) + self.Vclimb=rate +end + +--- Set the angle of descent. Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel. +-- @param #RAT self +-- @param #number angle Angle of descent in degrees. Minimum is 0.5 deg. Maximum 50 deg. +function RAT:SetDescentAngle(angle) + angle=angle or self.AlphaDescent + angle=math.max(angle, 0.5) + angle=math.min(angle, 50) + self.AlphaDescent=angle +end + +--- Set rules of engagement (ROE). Default is weapon hold. This is a peaceful class. +-- @param #RAT self +-- @param #string roe "hold" = weapon hold, "return" = return fire, "free" = weapons free. +function RAT:SetROE(roe) + if roe=="return" then + self.roe=RAT.ROE.returnfire + elseif roe=="free" then + self.roe=RAT.ROE.weaponfree + else + self.roe=RAT.ROE.weaponhold + end +end + +--- Set reaction to threat (ROT). Default is no reaction, i.e. aircraft will simply ignore all enemies. +-- @param #RAT self +-- @param #string rot "noreaction" = no reaction to threats, "passive" = passive defence, "evade" = evade enemy attacks. +function RAT:SetROT(rot) + if rot=="passive" then + self.rot=RAT.ROT.passive + elseif rot=="evade" then + self.rot=RAT.ROT.evade + else + self.rot=RAT.ROT.noreaction + end +end + +--- Set the name of the F10 submenu. Default is the name of the template group. +-- @param #RAT self +-- @param #string name Submenu name. +function RAT:MenuName(name) + self.SubMenuName=tostring(name) +end + +--- Enable ATC, which manages the landing queue for RAT aircraft if they arrive simultaniously at the same airport. +-- @param #RAT self +-- @param #boolean switch Enable ATC (true) or Disable ATC (false). No argument means ATC enabled. +function RAT:EnableATC(switch) + if switch==nil then + switch=true + end + self.ATCswitch=switch +end + +--- Max number of planes that get landing clearance of the RAT ATC. This setting effects all RAT objects and groups! +-- @param #RAT self +-- @param #number n Number of aircraft that are allowed to land simultaniously. Default is 2. +function RAT:ATC_Clearance(n) + RAT.ATC.Nclearance=n or 2 +end + +--- Delay between granting landing clearance for simultanious landings. This setting effects all RAT objects and groups! +-- @param #RAT self +-- @param #number time Delay time when the next aircraft will get landing clearance event if the previous one did not land yet. Default is 240 sec. +function RAT:ATC_Delay(time) + RAT.ATC.delay=time or 240 +end + +--- Set minimum distance between departure and destination. Default is 5 km. +-- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different. +-- @param #RAT self +-- @param #number dist Distance in km. +function RAT:SetMinDistance(dist) + -- Distance in meters. Absolute minimum is 500 m. + self.mindist=math.max(500, dist*1000) +end + +--- Set maximum distance between departure and destination. Default is 5000 km but aircarft range is also taken into account automatically. +-- @param #RAT self +-- @param #number dist Distance in km. +function RAT:SetMaxDistance(dist) + -- Distance in meters. + self.maxdist=dist*1000 +end + +--- Turn debug messages on or off. Default is off. +-- @param #RAT self +-- @param #boolean switch Turn debug on=true or off=false. No argument means on. +function RAT:_Debug(switch) + if switch==nil then + switch=true + end + self.debug=switch +end + +--- Aircraft report status update messages along the route. +-- @param #RAT self +-- @param #boolean switch Swtich reports on (true) or off (false). No argument is on. +function RAT:StatusReports(switch) + if switch==nil then + switch=true + end + self.reportstatus=switch +end + +--- Place markers of waypoints on the F10 map. Default is off. +-- @param #RAT self +-- @param #boolean switch true=yes, false=no. +function RAT:PlaceMarkers(switch) + if switch==nil then + switch=true + end + self.placemarkers=switch +end + +--- Set flight level. Setting this value will overrule all other logic. Aircraft will try to fly at this height regardless. +-- @param #RAT self +-- @param #number FL Fight Level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +function RAT:SetFL(FL) + FL=FL or self.FLcruise + FL=math.max(FL,0) + self.FLuser=FL*RAT.unit.FL2m +end + +--- Set max flight level. Setting this value will overrule all other logic. Aircraft will try to fly at less than this FL regardless. +-- @param #RAT self +-- @param #number FL Maximum Fight Level in hundrets of feet. +function RAT:SetFLmax(FL) + self.FLmaxuser=FL*RAT.unit.FL2m +end + +--- Set max cruising altitude above sea level. +-- @param #RAT self +-- @param #number alt Altitude ASL in meters. +function RAT:SetMaxCruiseAltitude(alt) + self.FLmaxuser=alt +end + +--- Set min flight level. Setting this value will overrule all other logic. Aircraft will try to fly at higher than this FL regardless. +-- @param #RAT self +-- @param #number FL Maximum Fight Level in hundrets of feet. +function RAT:SetFLmin(FL) + self.FLminuser=FL*RAT.unit.FL2m +end + +--- Set min cruising altitude above sea level. +-- @param #RAT self +-- @param #number alt Altitude ASL in meters. +function RAT:SetMinCruiseAltitude(alt) + self.FLminuser=alt +end + +--- Set flight level of cruising part. This is still be checked for consitancy with selected route and prone to radomization. +-- Default is FL200 for planes and FL005 for helicopters. +-- @param #RAT self +-- @param #number FL Flight level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +function RAT:SetFLcruise(FL) + self.FLcruise=FL*RAT.unit.FL2m +end + +--- Set cruising altitude. This is still be checked for consitancy with selected route and prone to radomization. +-- @param #RAT self +-- @param #number alt Cruising altitude ASL in meters. +function RAT:SetCruiseAltitude(alt) + self.FLcruise=alt +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Initialize basic parameters of the aircraft based on its (template) group in the mission editor. +-- @param #RAT self +-- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor. +function RAT:_InitAircraft(DCSgroup) + + local DCSunit=DCSgroup:getUnit(1) + local DCSdesc=DCSunit:getDesc() + local DCScategory=DCSgroup:getCategory() + local DCStype=DCSunit:getTypeName() + + -- Descriptors table of unit. + if self.debug then + self:E({"DCSdesc", DCSdesc}) + end + + -- set category + if DCScategory==Group.Category.AIRPLANE then + self.category=RAT.cat.plane + elseif DCScategory==Group.Category.HELICOPTER then + self.category=RAT.cat.heli + else + self.category="other" + env.error(RAT.id.."Group of RAT is neither airplane nor helicopter!") + end + + -- Get type of aircraft. + self.aircraft.type=DCStype + + -- inital fuel in % + self.aircraft.fuel=DCSunit:getFuel() + + -- operational range in NM converted to m + self.aircraft.Rmax = DCSdesc.range*RAT.unit.nm2m + + -- effective range taking fuel into accound and a 5% reserve + self.aircraft.Reff = self.aircraft.Rmax*self.aircraft.fuel*0.95 + + -- max airspeed from group + self.aircraft.Vmax = DCSdesc.speedMax + + -- max climb speed in m/s + self.aircraft.Vymax=DCSdesc.VyMax + + -- service ceiling in meters + self.aircraft.ceiling=DCSdesc.Hmax + + -- info message + local text=string.format("\n******************************************************\n") + text=text..string.format("Aircraft parameters:\n") + text=text..string.format("Template group = %s\n", self.SpawnTemplatePrefix) + text=text..string.format("Alias = %s\n", self.alias) + text=text..string.format("Category = %s\n", self.category) + text=text..string.format("Type = %s\n", self.aircraft.type) + text=text..string.format("Max air speed = %6.1f m/s\n", self.aircraft.Vmax) + text=text..string.format("Max climb speed = %6.1f m/s\n", self.aircraft.Vymax) + text=text..string.format("Initial Fuel = %6.1f\n", self.aircraft.fuel*100) + text=text..string.format("Max range = %6.1f km\n", self.aircraft.Rmax/1000) + text=text..string.format("Eff range = %6.1f km (with 95 percent initial fuel amount)\n", self.aircraft.Reff/1000) + text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n", self.aircraft.ceiling/1000, self.aircraft.ceiling/RAT.unit.FL2m) + text=text..string.format("******************************************************\n") + env.info(RAT.id..text) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Spawn the AI aircraft with a route. +-- Sets the departure and destination airports and waypoints. +-- Modifies the spawn template. +-- Sets ROE/ROT. +-- Initializes the ratcraft array and group menu. +-- @param #RAT self +-- @param #string _departure (Optional) Name of departure airbase. +-- @param #string _destination (Optional) Name of destination airbase. +-- @param #number _takeoff Takeoff type id. +function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint) + + -- Set takeoff type. + local takeoff=self.takeoff + local landing=self.landing + + -- Overrule takeoff/landing by what comes in. + if _takeoff then + takeoff=_takeoff + end + if _landing then + landing=_landing + end + + -- Random choice between cold and hot. + if takeoff==RAT.wp.coldorhot then + local temp={RAT.wp.cold, RAT.wp.hot} + takeoff=temp[math.random(2)] + end + + -- Set flight plan. + local departure, destination, waypoints, WPholding, WPfinal = self:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) + + -- Return nil if we could not find a departure destination or waypoints + if not (departure and destination and waypoints) then + return nil + end + + -- Set (another) livery. + local livery + if _livery then + -- Take livery from previous flight (continue journey). + livery=_livery + elseif self.livery then + -- Choose random livery. + livery=self.livery[math.random(#self.livery)] + local text=string.format("Chosen livery for group %s: %s", self:_AnticipatedGroupName(), livery) + env.info(RAT.id..text) + else + livery=nil + end + +--[[ + -- Use last waypoint of previous flight as initial wp for this one. + if _waypoint and takeoff==RAT.wp.air and (self.continuejourney or self.commute) then + -- If the other way does not work, we can still try this. + waypoints[1]=_waypoint + end +]] + + -- Modify the spawn template to follow the flight plan. + self:_ModifySpawnTemplate(waypoints, livery) + + -- Actually spawn the group. + local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP + + -- Increase group counter. + self.alive=self.alive+1 + + -- ATC is monitoring this flight (if it supposed to land). + if self.ATCswitch and landing==RAT.wp.landing then + if self.returnzone then + RAT:_ATCAddFlight(group:GetName(), departure:GetName()) + else + RAT:_ATCAddFlight(group:GetName(), destination:GetName()) + end + end + + -- Place markers of waypoints on F10 map. + if self.placemarkers then + self:_PlaceMarkers(waypoints, self.SpawnIndex) + end + + -- Set ROE, default is "weapon hold". + self:_SetROE(group, self.roe) + + -- Set ROT, default is "no reaction". + self:_SetROT(group, self.rot) + + -- Init ratcraft array. + self.ratcraft[self.SpawnIndex]={} + self.ratcraft[self.SpawnIndex]["group"]=group + self.ratcraft[self.SpawnIndex]["destination"]=destination + self.ratcraft[self.SpawnIndex]["departure"]=departure + self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints + self.ratcraft[self.SpawnIndex]["status"]="spawned" + self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir() + -- Time and position on ground. For check if aircraft is stuck somewhere. + if group:InAir() then + self.ratcraft[self.SpawnIndex]["Tground"]=nil + self.ratcraft[self.SpawnIndex]["Pground"]=nil + self.ratcraft[self.SpawnIndex]["Tlastcheck"]=nil + else + self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime() + self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate() + self.ratcraft[self.SpawnIndex]["Tlastcheck"]=timer.getTime() + end + -- Initial and current position. For calculating the travelled distance. + self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate() + self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate() + self.ratcraft[self.SpawnIndex]["Distance"]=0 + + -- Each aircraft gets its own takeoff type. + self.ratcraft[self.SpawnIndex].takeoff=takeoff + self.ratcraft[self.SpawnIndex].landing=landing + self.ratcraft[self.SpawnIndex].wpholding=WPholding + self.ratcraft[self.SpawnIndex].wpfinal=WPfinal + + -- Livery + self.ratcraft[self.SpawnIndex].livery=livery + + -- If this switch is set to true, the aircraft will be despawned the next time the status function is called. + self.ratcraft[self.SpawnIndex].despawnme=false + + + -- Create submenu for this group. + if self.f10menu then + local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex) + -- F10/RAT//Group X + self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SubMenuName].groups) + -- F10/RAT//Group X/Set ROE + self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE", self.Menu[self.SubMenuName].groups[self.SpawnIndex]) + MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponhold) + MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponfree) + MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.returnfire) + -- F10/RAT//Group X/Set ROT + self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups[self.SpawnIndex]) + MENU_MISSION_COMMAND:New("No reaction", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.noreaction) + MENU_MISSION_COMMAND:New("Passive defense", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.passive) + MENU_MISSION_COMMAND:New("Evade on fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.evade) + -- F10/RAT//Group X/ + MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._Despawn, self, group) + MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints, self.SpawnIndex) + MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex) + end + + return self.SpawnIndex + +end + + +--- Clear flight for landing. Sets tigger value to 1. +-- @param #RAT self +-- @param #string name Name of flight to be cleared for landing. +function RAT:ClearForLanding(name) + trigger.action.setUserFlag(name, 1) + local flagvalue=trigger.misc.getUserFlag(name) + env.info(RAT.id.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) +end + +--- Respawn a group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group to be repawned. +function RAT:_Respawn(group) + + -- Get the spawn index from group + local index=self:GetSpawnIndexFromGroup(group) + + -- Get departure and destination from previous journey. + local departure=self.ratcraft[index].departure + local destination=self.ratcraft[index].destination + local takeoff=self.ratcraft[index].takeoff + local landing=self.ratcraft[index].landing + local livery=self.ratcraft[index].livery + local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] + + local _departure=nil + local _destination=nil + local _takeoff=nil + local _landing=nil + local _livery=nil + local _lastwp=nil + + if self.continuejourney then + + -- We continue our journey from the old departure airport. + _departure=destination:GetName() + + -- Use the same livery for next aircraft. + _livery=livery + + if self.destinationzone then + + -- Case: X --> Zone --> Zone --> Zone + _takeoff=RAT.wp.air + _landing=RAT.wp.air + + elseif self.returnzone then + + -- Case: X --> Zone --> X, X --> Zone --> X + -- We flew to a zone and back. Takeoff type does not change. + _takeoff=self.takeoff + + -- If we took of in air we also want to land "in air". + if self.takeoff==RAT.wp.air then + _landing=RAT.wp.air + else + _landing=RAT.wp.landing + end + + -- Departure stays the same. (The destination is the zone here.) + _departure=departure:GetName() + + else + + -- Default case. Takeoff and landing type does not change. + _takeoff=self.takeoff + _landing=self.landing + + end + + elseif self.commute then + + -- We commute between departure and destination. + _departure=destination:GetName() + _destination=departure:GetName() + + -- Use the same livery for next aircraft. + _livery=livery + + -- Handle takeoff type. + if self.destinationzone then + -- self.takeoff is either RAT.wp.air or RAT.wp.cold + -- self.landing is RAT.wp.Air + + if self.takeoff==RAT.wp.air then + + -- Case: Zone <--> Zone (both have takeoff air) + _takeoff=RAT.wp.air -- = self.takeoff (because we just checked) + _landing=RAT.wp.air -- = self.landing (because destinationzone) + + else + + -- Case: Airport <--> Zone + if takeoff==RAT.wp.air then + -- Last takeoff was air so we are at the airport now, takeoff is from ground. + _takeoff=self.takeoff -- must be either hot/cold/runway/hotcold + _landing=RAT.wp.air -- must be air = self.landing (because destinationzone) + else + -- Last takeoff was on ground so we are at a zone now ==> takeoff in air, landing at airport. + _takeoff=RAT.wp.air + _landing=RAT.wp.landing + end + + end + + elseif self.returnzone then + + -- We flew to a zone and back. No need to swap departure and destination. + _departure=departure:GetName() + _destination=destination:GetName() + + -- Takeoff and landing should also not change. + _takeoff=self.takeoff + _landing=self.landing + + end + + end + + -- Take the last waypoint as initial waypoint for next plane. + if _takeoff==RAT.wp.air and (self.continuejourney or self.commute) then + _lastwp=lastwp + end + + if self.debug then + env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %s, %s, %s", tostring(self.takeoff), tostring(takeoff), tostring(_takeoff))) + env.info(RAT.id..string.format("self.landing, landing, _landing = %s, %s, %s", tostring(self.landing), tostring(landing), tostring(_landing))) + end + + -- Spawn new group. + if self.respawn_delay then + SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff, _landing, _livery, _lastwp}, self.respawn_delay) + else + self:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _lastwp) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the route of the AI plane. Due to DCS landing bug, this has to be done before the unit is spawned. +-- @param #RAT self +-- @param takeoff #RAT.wp Takeoff type. Could also be air start. +-- @param landing #RAT.wp Landing type. Could also be a destination in air. +-- @param Wrapper.Airport#AIRBASE _departure (Optional) Departure airbase. +-- @param Wrapper.Airport#AIRBASE _destination (Optional) Destination airbase. +-- @return Wrapper.Airport#AIRBASE Departure airbase. +-- @return Wrapper.Airport#AIRBASE Destination airbase. +-- @return #table Table of flight plan waypoints. +-- @return #nil If no valid departure or destination airport could be found. +function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) + + -- Max cruise speed. + local VxCruiseMax + if self.Vcruisemax then + -- User input. + VxCruiseMax = min(self.Vcruisemax, self.aircraft.Vmax) + else + -- Max cruise speed 90% of Vmax or 900 km/h whichever is lower. + VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250) + end + + -- Min cruise speed 70% of max cruise or 600 km/h whichever is lower. + local VxCruiseMin = math.min(VxCruiseMax*0.70, 166) + + -- Cruise speed (randomized). Expectation value at midpoint between min and max. + local VxCruise = self:_Random_Gaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) + + -- Climb speed 90% ov Vmax but max 720 km/h. + local VxClimb = math.min(self.aircraft.Vmax*0.90, 200) + + -- Descent speed 60% of Vmax but max 500 km/h. + local VxDescent = math.min(self.aircraft.Vmax*0.60, 140) + + -- Holding speed is 90% of descent speed. + local VxHolding = VxDescent*0.9 + + -- Final leg is 90% of holding speed. + local VxFinal = VxHolding*0.9 + + -- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate. + local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60, self.aircraft.Vymax) + + -- Climb angle in rad. + local AlphaClimb=math.asin(VyClimb/VxClimb) + + -- Descent angle in rad. + local AlphaDescent=math.rad(self.AlphaDescent) + + -- Expected cruise level (peak of Gaussian distribution) + local FLcruise_expect=self.FLcruise + + + -- DEPARTURE AIRPORT + -- Departure airport or zone. + local departure=nil + if _departure then + if self:_AirportExists(_departure) then + -- Check if new departure is an airport. + departure=AIRBASE:FindByName(_departure) + -- If we spawn in air, we convert departure to a zone. + if takeoff == RAT.wp.air then + departure=departure:GetZone() + end + elseif self:_ZoneExists(_departure) then + -- If it's not an airport, check whether it's a zone. + departure=ZONE:New(_departure) + else + local text=string.format("ERROR: Specified departure airport %s does not exist for %s!", _departure, self.alias) + env.error(RAT.id..text) + end + + else + departure=self:_PickDeparture(takeoff) + end + + -- Return nil if no departure could be found. + if not departure then + local text=string.format("No valid departure airport could be found for %s.", self.alias) + MESSAGE:New(text, 60):ToAll() + env.error(RAT.id..text) + return nil + end + + -- Coordinates of departure point. + local Pdeparture + if takeoff==RAT.wp.air then + if _waypoint then + -- Use coordinates of previous flight (commute or journey). + Pdeparture=COORDINATE:New(_waypoint.x, _waypoint.alt, _waypoint.y) + else + -- For an air start, we take a random point within the spawn zone. + local vec2=departure:GetRandomVec2() + Pdeparture=COORDINATE:NewFromVec2(vec2) + end + else + Pdeparture=departure:GetCoordinate() + end + + -- Height ASL of departure point. + local H_departure + if takeoff==RAT.wp.air then + -- Absolute minimum AGL + local Hmin + if self.category==RAT.cat.plane then + Hmin=1000 + else + Hmin=50 + end + -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). + H_departure=self:_Randomize(FLcruise_expect*0.7, 0.3, Pdeparture.y+Hmin, FLcruise_expect) + if self.FLminuser then + H_departure=math.max(H_departure,self.FLminuser) + end + -- Use alt of last flight. + if _waypoint then + H_departure=_waypoint.alt + end + else + H_departure=Pdeparture.y + end + + -- Adjust min distance between departure and destination for user set min flight level. + local mindist=self.mindist + if self.FLminuser then + + -- We can conly consider the symmetric case, because no destination selected yet. + local hclimb=self.FLminuser-H_departure + local hdescent=self.FLminuser-H_departure + + -- Minimum distance for l + local Dclimb, Ddescent, Dtot=self:_MinDistance(AlphaClimb, AlphaDescent, hclimb, hdescent) + + if takeoff==RAT.wp.air and landing==RAT.wpair then + mindist=0 -- Takeoff and landing are in air. No mindist required. + elseif takeoff==RAT.wp.air then + mindist=Ddescent -- Takeoff in air. Need only space to descent. + elseif landing==RAT.wp.air then + mindist=Dclimb -- Landing "in air". Need only space to climb. + else + mindist=Dtot -- Takeoff and landing on ground. Need both space to climb and descent. + end + + -- Mindist is at least self.mindist. + mindist=math.max(self.mindist, mindist) + + local text=string.format("Adjusting min distance to %d km (for given min FL%03d)", mindist/1000, self.FLminuser/RAT.unit.FL2m) + env.info(RAT.id..text) + end + + -- DESTINATION AIRPORT + local destination=nil + if _destination then + + if self:_AirportExists(_destination) then + + destination=AIRBASE:FindByName(_destination) + if landing==RAT.wp.air or self.returnzone then + destination=destination:GetZone() + end + + elseif self:_ZoneExists(_destination) then + destination=ZONE:New(_destination) + else + local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!", _destination, self.alias) + env.error(RAT.id..text) + end + + else + + -- This handles the case where we have a journey and the first flight is done, i.e. _departure is set. + -- If a user specified more than two destination airport explicitly, then we will stick to this. + -- Otherwise, the route is random from now on. + local random=self.random_destination + if self.continuejourney and _departure and #self.destination_ports<3 then + random=true + end + + -- In case of a returnzone the destination (i.e. return point) is always a zone. + local mylanding=landing + local acrange=self.aircraft.Reff + if self.returnzone then + mylanding=RAT.wp.air + acrange=self.aircraft.Reff/2 -- Aircraft needs to go to zone and back home. + end + + -- Pick a destination airport. + destination=self:_PickDestination(departure, Pdeparture, mindist, math.min(acrange, self.maxdist), random, mylanding) + end + + -- Return nil if no departure could be found. + if not destination then + local text=string.format("No valid destination airport could be found for %s!", self.alias) + MESSAGE:New(text, 60):ToAll() + env.error(RAT.id..text) + return nil + end + + -- Check that departure and destination are not the same. Should not happen due to mindist. + if destination:GetName()==departure:GetName() then + local text=string.format("%s: Destination and departure are identical. Airport/zone %s.", self.alias, destination:GetName()) + MESSAGE:New(text, 30):ToAll() + env.error(RAT.id..text) + end + + -- Get a random point inside zone return zone. + local Preturn + local destination_returnzone + if self.returnzone then + -- Get a random point inside zone return zone. + local vec2=destination:GetRandomVec2() + Preturn=COORDINATE:NewFromVec2(vec2) + -- Returnzone becomes destination. + destination_returnzone=destination + -- Set departure to destination. + destination=departure + end + + -- Get destination coordinate. Either in a zone or exactly at the airport. + local Pdestination + if landing==RAT.wp.air then + local vec2=destination:GetRandomVec2() + Pdestination=COORDINATE:NewFromVec2(vec2) + else + Pdestination=destination:GetCoordinate() + end + + -- Height ASL of destination airport/zone. + local H_destination=Pdestination.y + + -- DESCENT/HOLDING POINT + -- Get a random point between 5 and 10 km away from the destination. + local Rhmin=8000 + local Rhmax=20000 + if self.category==RAT.cat.heli then + -- For helos we set a distance between 500 to 1000 m. + Rhmin=500 + Rhmax=1000 + end + + -- Coordinates of the holding point. y is the land height at that point. + local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) + local Pholding=COORDINATE:NewFromVec2(Vholding) + + -- AGL height of holding point. + local H_holding=Pholding.y + + -- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL. + local h_holding + if self.category==RAT.cat.plane then + h_holding=1200 + else + h_holding=150 + end + h_holding=self:_Randomize(h_holding, 0.2) + + -- This is the actual height ASL of the holding point we want to fly to + local Hh_holding=H_holding+h_holding + + -- When we dont land, we set the holding altitude to the departure or cruise alt. + -- This is used in the calculations. + if landing==RAT.wp.air then + Hh_holding=H_departure + end + + -- Distance from holding point to final destination. + local d_holding=Pholding:Get2DDistance(Pdestination) + + -- GENERAL + local heading + local d_total + if self.returnzone then + + -- Heading from departure to destination in return zone. + heading=self:_Course(Pdeparture, Preturn) + + -- Total distance to return zone and back. + d_total=Pdeparture:Get2DDistance(Preturn) + Preturn:Get2DDistance(Pholding) + + else + -- Heading from departure to holding point of destination. + heading=self:_Course(Pdeparture, Pholding) + + -- Total distance between departure and holding point near destination. + d_total=Pdeparture:Get2DDistance(Pholding) + end + + -- Max height in case of air start, i.e. if we only would descent to holding point for the given distance. + if takeoff==RAT.wp.air then + local H_departure_max + if landing==RAT.wp.air then + H_departure_max = H_departure -- If we fly to a zone, there is no descent necessary. + else + H_departure_max = d_total * math.tan(AlphaDescent) + Hh_holding + end + H_departure=math.min(H_departure, H_departure_max) + end + + -------------------------------------------- + + -- Height difference between departure and destination. + local deltaH=math.abs(H_departure-Hh_holding) + + -- Slope between departure and destination. + local phi = math.atan(deltaH/d_total) + + -- Adjusted climb/descent angles. + local phi_climb + local phi_descent + if (H_departure > Hh_holding) then + phi_climb=AlphaClimb+phi + phi_descent=AlphaDescent-phi + else + phi_climb=AlphaClimb-phi + phi_descent=AlphaDescent+phi + end + + -- Total distance including slope. + local D_total + if self.returnzone then + D_total = math.sqrt(deltaH*deltaH+d_total/2*d_total/2) + else + D_total = math.sqrt(deltaH*deltaH+d_total*d_total) + end + + -- SSA triangle for sloped case. + local gamma=math.rad(180)-phi_climb-phi_descent + local a = D_total*math.sin(phi_climb)/math.sin(gamma) + local b = D_total*math.sin(phi_descent)/math.sin(gamma) + local hphi_max = b*math.sin(phi_climb) + local hphi_max2 = a*math.sin(phi_descent) + + -- Height of triangle. + local h_max1 = b*math.sin(AlphaClimb) + local h_max2 = a*math.sin(AlphaDescent) + + -- Max height relative to departure or destination. + local h_max + if (H_departure > Hh_holding) then + h_max=math.min(h_max1, h_max2) + else + h_max=math.max(h_max1, h_max2) + end + + -- Max flight level aircraft can reach for given angles and distance. + local FLmax = h_max+H_departure + + --CRUISE + -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. + local FLmin=math.max(H_departure, Hh_holding) + + -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. + if self.category==RAT.cat.heli then + FLmin=math.max(H_departure, H_destination)+50 + FLmax=math.max(H_departure, H_destination)+1000 + end + + -- Ensure that FLmax not above its service ceiling. + FLmax=math.min(FLmax, self.aircraft.ceiling) + + -- Overrule setting if user specified min/max flight level explicitly. + if self.FLminuser then + FLmin=math.max(self.FLminuser, FLmin) -- Still take care that we dont fly too high. + end + if self.FLmaxuser then + FLmax=math.min(self.FLmaxuser, FLmax) -- Still take care that we dont fly too low. + end + + -- If the route is very short we set FLmin a bit lower than FLmax. + if FLmin>FLmax then + FLmin=FLmax + end + + -- Expected cruise altitude - peak of gaussian distribution. + if FLcruise_expectFLmax then + FLcruise_expect=FLmax + end + + -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. + local FLcruise=self:_Random_Gaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) + + -- Overrule setting if user specified a flight level explicitly. + if self.FLuser then + FLcruise=self.FLuser + -- Still cruise alt should be with parameters! + FLcruise=math.max(FLcruise, FLmin) + FLcruise=math.min(FLcruise, FLmax) + end + + -- Climb and descent heights. + local h_climb = FLcruise - H_departure + local h_descent = FLcruise - Hh_holding + + -- Distances. + local d_climb = h_climb/math.tan(AlphaClimb) + local d_descent = h_descent/math.tan(AlphaDescent) + local d_cruise = d_total-d_climb-d_descent + + -- debug message + local text=string.format("\n******************************************************\n") + text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) + text=text..string.format("Alias = %s\n", self.alias) + text=text..string.format("Group name = %s\n\n", self:_AnticipatedGroupName()) + text=text..string.format("Speeds:\n") + text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n", VxCruiseMin, VxCruiseMin*3.6) + text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n", VxCruiseMax, VxCruiseMax*3.6) + text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n", VxCruise, VxCruise*3.6) + text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n", VxClimb, VxClimb*3.6) + text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n", VxDescent, VxDescent*3.6) + text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n", VxHolding, VxHolding*3.6) + text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n", VxFinal, VxFinal*3.6) + text=text..string.format("VyClimb = %6.1f m/s\n", VyClimb) + text=text..string.format("\nDistances:\n") + text=text..string.format("d_climb = %6.1f km\n", d_climb/1000) + text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000) + text=text..string.format("d_descent = %6.1f km\n", d_descent/1000) + text=text..string.format("d_holding = %6.1f km\n", d_holding/1000) + text=text..string.format("d_total = %6.1f km\n", d_total/1000) + text=text..string.format("\nHeights:\n") + text=text..string.format("H_departure = %6.1f m ASL\n", H_departure) + text=text..string.format("H_destination = %6.1f m ASL\n", H_destination) + text=text..string.format("H_holding = %6.1f m ASL\n", H_holding) + text=text..string.format("h_climb = %6.1f m\n", h_climb) + text=text..string.format("h_descent = %6.1f m\n", h_descent) + text=text..string.format("h_holding = %6.1f m\n", h_holding) + text=text..string.format("delta H = %6.1f m\n", deltaH) + text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n", FLmin, FLmin/RAT.unit.FL2m) + text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n", FLcruise, FLcruise/RAT.unit.FL2m) + text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n", FLmax, FLmax/RAT.unit.FL2m) + text=text..string.format("\nAngles:\n") + text=text..string.format("Alpha climb = %6.2f Deg\n", math.deg(AlphaClimb)) + text=text..string.format("Alpha descent = %6.2f Deg\n", math.deg(AlphaDescent)) + text=text..string.format("Phi (slope) = %6.2f Deg\n", math.deg(phi)) + text=text..string.format("Phi climb = %6.2f Deg\n", math.deg(phi_climb)) + text=text..string.format("Phi descent = %6.2f Deg\n", math.deg(phi_descent)) + if self.debug then + -- Max heights and distances if we would travel at FLmax. + local h_climb_max = FLmax - H_departure + local h_descent_max = FLmax - Hh_holding + local d_climb_max = h_climb_max/math.tan(AlphaClimb) + local d_descent_max = h_descent_max/math.tan(AlphaDescent) + local d_cruise_max = d_total-d_climb_max-d_descent_max + text=text..string.format("Heading = %6.1f Deg\n", heading) + text=text..string.format("\nSSA triangle:\n") + text=text..string.format("D_total = %6.1f km\n", D_total/1000) + text=text..string.format("gamma = %6.1f Deg\n", math.deg(gamma)) + text=text..string.format("a = %6.1f m\n", a) + text=text..string.format("b = %6.1f m\n", b) + text=text..string.format("hphi_max = %6.1f m\n", hphi_max) + text=text..string.format("hphi_max2 = %6.1f m\n", hphi_max2) + text=text..string.format("h_max1 = %6.1f m\n", h_max1) + text=text..string.format("h_max2 = %6.1f m\n", h_max2) + text=text..string.format("h_max = %6.1f m\n", h_max) + text=text..string.format("\nMax heights and distances:\n") + text=text..string.format("d_climb_max = %6.1f km\n", d_climb_max/1000) + text=text..string.format("d_cruise_max = %6.1f km\n", d_cruise_max/1000) + text=text..string.format("d_descent_max = %6.1f km\n", d_descent_max/1000) + text=text..string.format("h_climb_max = %6.1f m\n", h_climb_max) + text=text..string.format("h_descent_max = %6.1f m\n", h_descent_max) + end + text=text..string.format("******************************************************\n") + env.info(RAT.id..text) + + -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. + if d_cruise<0 then + d_cruise=100 + end + + -- Waypoints and coordinates + local wp={} + local c={} + local wpholding=nil + local wpfinal=nil + + -- Departure/Take-off + c[#c+1]=Pdeparture + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c[#wp+1], VxClimb, H_departure, departure) + self.waypointdescriptions[#wp]="Departure" + self.waypointstatus[#wp]=RAT.status.Departure + + -- Climb + if takeoff==RAT.wp.air then + + -- Air start. + if d_climb < 5000 or d_cruise < 5000 then + -- We omit the climb phase completely and add it to the cruise part. + d_cruise=d_cruise+d_climb + else + -- Only one waypoint at the end of climb = begin of cruise. + c[#c+1]=c[#c]:Translate(d_climb, heading) + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Begin of Cruise" + self.waypointstatus[#wp]=RAT.status.Cruise + end + + else + + -- Ground start. + c[#c+1]=c[#c]:Translate(d_climb/2, heading) + c[#c+1]=c[#c]:Translate(d_climb/2, heading) + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c[#wp+1], VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[#wp]="Climb" + self.waypointstatus[#wp]=RAT.status.Climb + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Begin of Cruise" + self.waypointstatus[#wp]=RAT.status.Cruise + + end + + -- Cruise + + -- First add the little bit from begin of cruise to the return point. + if self.returnzone then + c[#c+1]=Preturn + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Return Zone" + self.waypointstatus[#wp]=RAT.status.Uturn + end + + if landing==RAT.wp.air then + + -- Next waypoint is already the final destination. + c[#c+1]=Pdestination + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Final Destination" + self.waypointstatus[#wp]=RAT.status.Destination + + elseif self.returnzone then + + -- The little bit back to end of cruise. + c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="End of Cruise" + self.waypointstatus[#wp]=RAT.status.Descent + + else + + c[#c+1]=c[#c]:Translate(d_cruise, heading) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="End of Cruise" + self.waypointstatus[#wp]=RAT.status.Descent + + end + + -- Descent (only if we acually want to land) + if landing==RAT.wp.landing then + if self.returnzone then + c[#c+1]=c[#c]:Translate(d_descent/2, heading-180) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + else + c[#c+1]=c[#c]:Translate(d_descent/2, heading) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + end + end + + -- Holding and final destination. + if landing==RAT.wp.landing then + + -- Holding point + c[#c+1]=Pholding + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp]="Holding Point" + self.waypointstatus[#wp]=RAT.status.Holding + wpholding=#wp + + -- Final destination. + c[#c+1]=Pdestination + wp[#wp+1]=self:_Waypoint(#wp+1, landing, c[#wp+1], VxFinal, H_destination, destination) + self.waypointdescriptions[#wp]="Destination" + self.waypointstatus[#wp]=RAT.status.Destination + + end + + -- Final Waypoint + wpfinal=#wp + + -- Fill table with waypoints. + local waypoints={} + for _,p in ipairs(wp) do + table.insert(waypoints, p) + end + + -- Some info on the route. + self:_Routeinfo(waypoints, "Waypoint info in set_route:") + + -- Return departure, destination and waypoints. + if self.returnzone then + -- We return the actual zone here because returning the departure leads to problems with commute. + return departure, destination_returnzone, waypoints, wpholding, wpfinal + else + return departure, destination, waypoints, wpholding, wpfinal + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly. +-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input. +-- @param #RAT self +-- @param #number takeoff Takeoff type. +-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport. +-- @return Core.Zone#ZONE Departure zone if spawning in air. +function RAT:_PickDeparture(takeoff) + + -- Array of possible departure airports or zones. + local departures={} + + if self.random_departure then + + -- Airports of friendly coalitions. + for _,airport in pairs(self.airports) do + + local name=airport:GetName() + if not self:_Excluded(name) then + if takeoff==RAT.wp.air then + table.insert(departures, airport:GetZone()) -- insert zone object. + else + table.insert(departures, airport) -- insert airport object. + end + end + + end + + else + + -- Destination airports or zones specified by user. + for _,name in pairs(self.departure_ports) do + + local dep=nil + if self:_AirportExists(name) then + if takeoff==RAT.wp.air then + dep=AIRBASE:FindByName(name):GetZone() + else + dep=AIRBASE:FindByName(name) + end + elseif self:_ZoneExists(name) then + if takeoff==RAT.wp.air then + dep=ZONE:New(name) + else + env.error(RAT.id.."Takeoff is not in air. Cannot use "..name.." as departure!") + end + else + env.error(RAT.id.."No airport or zone found with name "..name) + end + + -- Add to departures table. + if dep then + table.insert(departures, dep) + end + + end + + end + + -- Info message. + env.info(RAT.id.."Number of possible departures = "..#departures) + + -- Select departure airport or zone. + local departure=departures[math.random(#departures)] + + local text + if departure and departure:GetName() then + if takeoff==RAT.wp.air then + text="Chosen departure zone: "..departure:GetName() + else + text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")" + end + env.info(RAT.id..text) + if self.debug then + MESSAGE:New(text, 30):ToAll() + end + else + env.error(RAT.id.."No departure airport or zone found.") + departure=nil + end + + return departure +end + +--- Pick destination airport or zone depending on departure position. +-- @param #RAT self +-- @param Wrapper.Airbase#AIRBASE departure Departure airport or zone. +-- @param Core.Point#COORDINATE q Coordinate of the departure point. +-- @param #number minrange Minimum range to q in meters. +-- @param #number maxrange Maximum range to q in meters. +-- @param #boolean random Destination is randomly selected from friendly airport (true) or from destinations specified by user input (false). +-- @param #number landing Number indicating whether we land at a destination airport or fly to a zone object. +-- @return Wrapper.Airbase#AIRBASE destination Destination airport or zone. +function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) + + -- Min/max range to destination. + minrange=minrange or self.mindist + maxrange=maxrange or self.maxdist + + -- All possible destinations. + local destinations={} + + if random then + + -- Airports of friendly coalitions. + for _,airport in pairs(self.airports) do + local name=airport:GetName() + if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then + + -- Distance from departure to possible destination + local distance=q:Get2DDistance(airport:GetCoordinate()) + + -- Check if distance form departure to destination is within min/max range. + if distance>=minrange and distance<=maxrange then + if landing==RAT.wp.air then + table.insert(destinations, airport:GetZone()) -- insert zone object. + else + table.insert(destinations, airport) -- insert airport object. + end + end + end + end + + else + + -- Destination airports or zones specified by user. + for _,name in pairs(self.destination_ports) do + + -- Make sure departure and destination are not identical. + if name ~= departure:GetName() then + + local dest=nil + if self:_AirportExists(name) then + if landing==RAT.wp.air then + dest=AIRBASE:FindByName(name):GetZone() + else + dest=AIRBASE:FindByName(name) + end + elseif self:_ZoneExists(name) then + if landing==RAT.wp.air then + dest=ZONE:New(name) + else + env.error(RAT.id.."Landing is not in air. Cannot use zone "..name.." as destination!") + end + else + env.error(RAT.id.."No airport or zone found with name "..name) + end + + if dest then + -- Distance from departure to possible destination + local distance=q:Get2DDistance(dest:GetCoordinate()) + + -- Add as possible destination if zone is within range. + if distance>=minrange and distance<=maxrange then + table.insert(destinations, dest) + else + local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.", name, distance, minrange, maxrange) + env.info(RAT.id..text) + end + end + + end + end + end + + -- Info message. + env.info(RAT.id.."Number of possible destinations = "..#destinations) + + if #destinations > 0 then + --- Compare distance of destination airports. + -- @param Core.Point#COORDINATE a Coordinate of point a. + -- @param Core.Point#COORDINATE b Coordinate of point b. + -- @return #list Table sorted by distance. + local function compare(a,b) + local qa=q:Get2DDistance(a:GetCoordinate()) + local qb=q:Get2DDistance(b:GetCoordinate()) + return qa < qb + end + table.sort(destinations, compare) + else + destinations=nil + end + + + -- Randomly select one possible destination. + local destination + if destinations and #destinations>0 then + + -- Random selection. + destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE + + -- Debug message. + local text + if landing==RAT.wp.air then + text=string.format("Chosen destination zone: %s.", destination:GetName()) + else + text=string.format("Chosen destination airport: %s (ID %d).", destination:GetName(), destination:GetID()) + end + env.info(RAT.id..text) + if self.debug then + MESSAGE:New(text, 30):ToAll() + end + + else + env.error(RAT.id.."No destination airport or zone found.") + destination=nil + end + + -- Return the chosen destination. + return destination + +end + +--- Find airports within a zone. +-- @param #RAT self +-- @param Core.Zone#ZONE zone +-- @return #list Table with airport names that lie within the zone. +function RAT:_GetAirportsInZone(zone) + local airports={} + for _,airport in pairs(self.airports) do + local name=airport:GetName() + local coord=airport:GetCoordinate() + + if zone:IsPointVec3InZone(coord) then + table.insert(airports, name) + end + end + return airports +end + +--- Check if airport is excluded from possible departures and destinations. +-- @param #RAT self +-- @param #string port Name of airport, FARP or ship to check. +-- @return #boolean true if airport is excluded and false otherwise. +function RAT:_Excluded(port) + for _,name in pairs(self.excluded_ports) do + if name==port then + return true + end + end + return false +end + +--- Check if airport is friendly, i.e. belongs to the right coalition. +-- @param #RAT self +-- @param #string port Name of airport, FARP or ship to check. +-- @return #boolean true if airport is friendly and false otherwise. +function RAT:_IsFriendly(port) + for _,airport in pairs(self.airports) do + local name=airport:GetName() + if name==port then + return true + end + end + return false +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get all airports of the current map. +-- @param #RAT self +function RAT:_GetAirportsOfMap() + local _coalition + + for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE + + -- set coalition + if i==0 then + _coalition=coalition.side.NEUTRAL + elseif i==1 then + _coalition=coalition.side.RED + elseif i==2 then + _coalition=coalition.side.BLUE + end + + -- get airbases of coalition + local ab=coalition.getAirbases(i) + + -- loop over airbases and put them in a table + for _,airbase in pairs(ab) do + + local _id=airbase:getID() + local _p=airbase:getPosition().p + local _name=airbase:getName() + local _myab=AIRBASE:FindByName(_name) + + -- Add airport to table. + table.insert(self.airports_map, _myab) + + if self.debug then + local text1="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() + --local text2="DCS : Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName() + env.info(RAT.id..text1) + --env.info(RAT.id..text2) + end + + end + + end +end + +--- Get all "friendly" airports of the current map. +-- @param #RAT self +function RAT:_GetAirportsOfCoalition() + for _,coalition in pairs(self.ctable) do + for _,airport in pairs(self.airports_map) do + if airport:GetCoalition()==coalition then + -- Planes cannot land on FARPs. + local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP" + -- Planes cannot land on ships. + local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1 + if not (condition1 or condition2) then + table.insert(self.airports, airport) + end + end + end + end + + if #self.airports==0 then + local text="No possible departure/destination airports found!" + MESSAGE:New(text, 60):ToAll() + env.error(RAT.id..text) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Report status of RAT groups. +-- @param #RAT self +-- @param #boolean message (Optional) Send message with report to all if true. +-- @param #number forID (Optional) Send message only for this ID. +function RAT:Status(message, forID) + + message=message or false + forID=forID or false + + -- number of ratcraft spawned. + local ngroups=#self.ratcraft + + -- Current time. + local Tnow=timer.getTime() + + for i=1, ngroups do + + if self.ratcraft[i].group then + if self.ratcraft[i].group:IsAlive() then + + -- Gather some information. + local group=self.ratcraft[i].group --Wrapper.Group#GROUP + local prefix=self:_GetPrefixFromGroup(group) + local life=self:_GetLife(group) + local fuel=group:GetFuel()*100.0 + local airborne=group:InAir() + local coords=group:GetCoordinate() + local alt=coords.y + --local vel=group:GetVelocityKMH() + local departure=self.ratcraft[i].departure:GetName() + local destination=self.ratcraft[i].destination:GetName() + local type=self.aircraft.type + + + -- Monitor time and distance on ground. + local Tg=0 + local Dg=0 + local dTlast=0 + local stationary=false --lets assume, we did move + if airborne then + -- Aircraft is airborne. + self.ratcraft[i]["Tground"]=nil + self.ratcraft[i]["Pground"]=nil + self.ratcraft[i]["Tlastcheck"]=nil + else + --Aircraft is on ground. + if self.ratcraft[i]["Tground"] then + -- Aircraft was already on ground. Calculate total time on ground. + Tg=Tnow-self.ratcraft[i]["Tground"] + + -- Distance on ground since last check. + Dg=coords:Get2DDistance(self.ratcraft[i]["Pground"]) + + -- Time interval since last check. + dTlast=Tnow-self.ratcraft[i]["Tlastcheck"] + + -- If more than Tinactive seconds passed since last check ==> check how much we moved meanwhile. + if dTlast > self.Tinactive then + + -- If aircraft did not move more than 50 m since last check, we call it stationary and despawn it. + --TODO: add case that the aircraft are currently starting their engines. This should not count as being stationary. + --local starting_engines=self.ratcraft[i].status=="" + if Dg<50 and not self.uncontrolled then + stationary=true + end + + -- Set the current time to know when the next check is necessary. + self.ratcraft[i]["Tlastcheck"]=Tnow + self.ratcraft[i]["Pground"]=coords + end + + else + -- First time we see that the aircraft is on ground. Initialize the times and position. + self.ratcraft[i]["Tground"]=Tnow + self.ratcraft[i]["Tlastcheck"]=Tnow + self.ratcraft[i]["Pground"]=coords + end + end + + -- Monitor travelled distance since last check. + local Pn=coords + local Dtravel=Pn:Get2DDistance(self.ratcraft[i]["Pnow"]) + self.ratcraft[i]["Pnow"]=Pn + + -- Add up the travelled distance. + self.ratcraft[i]["Distance"]=self.ratcraft[i]["Distance"]+Dtravel + + -- Distance remaining to destination. + local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) + + -- Status shortcut. + local status=self.ratcraft[i].status + + -- Uncontrolled aircraft. + if self.uncontrolled then + status="Uncontrolled" + end + + -- Status report. + if (forID and i==forID) or (not forID) then + local text=string.format("ID %i of group %s\n", i, prefix) + if self.commute then + text=text..string.format("%s commuting between %s and %s\n", type, departure, destination) + elseif self.continuejourney then + text=text..string.format("%s travelling from %s to %s (and continueing form there)\n", type, departure, destination) + else + text=text..string.format("%s travelling from %s to %s\n", type, departure, destination) + end + text=text..string.format("Status: %s", status) + if airborne then + text=text.." [airborne]\n" + else + text=text.." [on ground]\n" + end + text=text..string.format("Fuel = %3.0f %%\n", fuel) + text=text..string.format("Life = %3.0f %%\n", life) + text=text..string.format("FL%03d = %i m ASL\n", alt/RAT.unit.FL2m, alt) + --text=text..string.format("Speed = %i km/h\n", vel) + text=text..string.format("Distance travelled = %6.1f km\n", self.ratcraft[i]["Distance"]/1000) + text=text..string.format("Distance to destination = %6.1f km", Ddestination/1000) + if not airborne then + text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg) + text=text..string.format("Position change = %8.1f m since %3.0f seconds.", Dg, dTlast) + end + if self.debug then + env.info(RAT.id..text) + end + if message then + MESSAGE:New(text, 20):ToAll() + end + end + + -- Despawn groups if they are on ground and don't move or are damaged. + if not airborne then + + -- Despawn unit if it did not move more then 50 m in the last 180 seconds. + if stationary then + local text=string.format("Group %s is despawned after being %4.0f seconds inaktive on ground.", self.alias, dTlast) + env.info(RAT.id..text) + self:_Despawn(group) + end + -- Despawn group if life is < 10% and distance travelled < 100 m. + if life<10 and Dtravel<100 then + local text=string.format("Damaged group %s is despawned. Life = %3.0f", self.alias, life) + self:_Despawn(group) + end + end + + if self.ratcraft[i].despawnme then + local text=string.format("Flight %s will be despawned NOW!", self.alias) + env.info(RAT.id..text) + -- Despawn old group. + self:_Respawn(self.ratcraft[i].group) + self:_Despawn(self.ratcraft[i].group) + end + end + else + if self.debug then + local text=string.format("Group %i does not exist.", i) + env.info(RAT.id..text) + end + end + end + + if (message and not forID) then + local text=string.format("Alive groups of %s: %d", self.alias, self.alive) + env.info(RAT.id..text) + MESSAGE:New(text, 20):ToAll() + end + +end + +--- Get (relative) life of first unit of a group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group of unit. +-- @return #number Life of unit in percent. +function RAT:_GetLife(group) + local life=0.0 + if group and group:IsAlive() then + local unit=group:GetUnit(1) + if unit then + life=unit:GetLife()/unit:GetLife0()*100 + else + if self.debug then + env.error(RAT.id.."Unit does not exist in RAT_Getlife(). Returning zero.") + end + end + else + if self.debug then + env.error(RAT.id.."Group does not exist in RAT_Getlife(). Returning zero.") + end + end + return life +end + +--- Set status of group. +-- @param #RAT self +function RAT:_SetStatus(group, status) + + -- Get index from groupname. + local index=self:GetSpawnIndexFromGroup(group) + + -- Set new status. + self.ratcraft[index].status=status + + -- No status update message for "first waypoint", "holding" + local no1 = status==RAT.status.Departure + local no2 = status==RAT.status.EventBirthAir + local no3 = status==RAT.status.Holding + + local text=string.format("Flight %s: %s.", group:GetName(), status) + env.info(RAT.id..text) + + if (not (no1 or no2 or no3)) then + MESSAGE:New(text, 10):ToAllIf(self.reportstatus) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function is executed when a unit is spawned. +-- @param #RAT self +function RAT:_OnBirth(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." was born." + env.info(RAT.id..text) + + -- Set status. + local status + if SpawnGroup:InAir() then + status="Just born (after air start)" + status=RAT.status.EventBirthAir + else + status="Starting engines (after birth)" + status=RAT.status.EventBirth + end + self:_SetStatus(SpawnGroup, status) + + end + end + else + if self.debug then + env.error("Group does not exist in RAT:_OnBirthDay().") + end + end +end + +--- Function is executed when a unit starts its engines. +-- @param #RAT self +function RAT:_EngineStartup(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." started engines." + env.info(RAT.id..text) + + -- Set status. + local status + if SpawnGroup:InAir() then + status="On journey (after air start)" + status=RAT.status.EventEngineStartAir + else + status="Taxiing (after engines started)" + status=RAT.status.EventEngineStart + end + self:_SetStatus(SpawnGroup, status) + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_EngineStartup().") + end + end +end + +--- Function is executed when a unit takes off. +-- @param #RAT self +function RAT:_OnTakeoff(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." is airborne." + env.info(RAT.id..text) + + -- Set status. + local status=RAT.status.EventTakeoff + --self:_SetStatus(SpawnGroup, "On journey (after takeoff)") + self:_SetStatus(SpawnGroup, status) + + if self.respawn_after_takeoff then + text="Event: Group "..SpawnGroup:GetName().." will be respawned." + env.info(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_OnTakeoff().") + end + end +end + +--- Function is executed when a unit lands. +-- @param #RAT self +function RAT:_OnLand(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." landed." + env.info(RAT.id..text) + + -- Set status. + --self:_SetStatus(SpawnGroup, "Taxiing (after landing)") + local status=RAT.status.EventLand + self:_SetStatus(SpawnGroup, status) + + -- ATC plane landed. Take it out of the queue and set runway to free. + if self.ATCswitch then + RAT:_ATCFlightLanded(SpawnGroup:GetName()) + end + + if self.respawn_at_landing and not self.norespawn then + text="Event: Group "..SpawnGroup:GetName().." will be respawned." + env.info(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_OnLand().") + end + end +end + +--- Function is executed when a unit shuts down its engines. +-- @param #RAT self +function RAT:_OnEngineShutdown(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." shut down its engines." + env.info(RAT.id..text) + + -- Set status. + --self:_SetStatus(SpawnGroup, "Parking (shutting down engines)") + local status=RAT.status.EventEngineShutdown + self:_SetStatus(SpawnGroup, status) + + if not self.respawn_at_landing and not self.norespawn then + text="Event: Group "..SpawnGroup:GetName().." will be respawned." + env.info(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + + -- Despawn group. + text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." + env.info(RAT.id..text) + self:_Despawn(SpawnGroup) + + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_OnEngineShutdown().") + end + end +end + +--- Function is executed when a unit is dead. +-- @param #RAT self +function RAT:_OnDead(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." died." + env.info(RAT.id..text) + + -- Set status. + --self:_SetStatus(SpawnGroup, "Destroyed (after dead)") + local status=RAT.status.EventDead + self:_SetStatus(SpawnGroup, status) + + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_OnDead().") + end + end +end + +--- Function is executed when a unit crashes. +-- @param #RAT self +function RAT:_OnCrash(EventData) + + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + + if SpawnGroup then + + env.info(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) + + -- Get the template name of the group. This can be nil if this was not a spawned group. + local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) + + if EventPrefix then + + -- Check that the template name actually belongs to this object. + if EventPrefix == self.alias then + + local text="Event: Group "..SpawnGroup:GetName().." crashed." + env.info(RAT.id..text) + + -- Set status. + --self:_SetStatus(SpawnGroup, "Crashed") + local status=RAT.status.EventCrash + self:_SetStatus(SpawnGroup, status) + + --TODO: Aircraft are not respawned if they crash. Should they? + + --TODO: Maybe spawn some people at the crash site and send a distress call. + -- And define them as cargo which can be rescued. + end + end + + else + if self.debug then + env.error("Group does not exist in RAT:_OnCrash().") + end + end +end + +--- Despawn unit. Unit gets destoyed and group is set to nil. +-- Index of ratcraft array is taken from spawned group name. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group to be despawned. +function RAT:_Despawn(group) + + local index=self:GetSpawnIndexFromGroup(group) + --self.ratcraft[index].group:Destroy() + self.ratcraft[index].group=nil + group:Destroy() + + -- Decrease group alive counter. + self.alive=self.alive-1 + + -- Remove submenu for this group. + if self.f10menu then + self.Menu[self.SubMenuName]["groups"][index]:Remove() + end + + --TODO: Maybe here could be some more arrays deleted? +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a waypoint that can be used with the Route command. +-- @param #RAT self +-- @param #number Running index of waypoints. Starts with 1 which is normally departure/spawn waypoint. +-- @param #number Type Type of waypoint. +-- @param Core.Point#COORDINATE Coord 3D coordinate of the waypoint. +-- @param #number Speed Speed in m/s. +-- @param #number Altitude Altitude in m. +-- @param Wrapper.Airbase#AIRBASE Airport Airport of object to spawn. +-- @return #table Waypoints for DCS task route or spawn template. +function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) + + -- Altitude of input parameter or y-component of 3D-coordinate. + local _Altitude=Altitude or Coord.y + + -- Land height at given coordinate. + local Hland=Coord:GetLandHeight() + + -- convert type and action in DCS format + local _Type=nil + local _Action=nil + local _alttype="RADIO" + + if Type==RAT.wp.cold then + -- take-off with engine off + _Type="TakeOffParking" + _Action="From Parking Area" + _Altitude = 0 + _alttype="RADIO" + elseif Type==RAT.wp.hot then + -- take-off with engine on + _Type="TakeOffParkingHot" + _Action="From Parking Area Hot" + _Altitude = 0 + _alttype="RADIO" + elseif Type==RAT.wp.runway then + -- take-off from runway + _Type="TakeOff" + _Action="From Parking Area" + _Altitude = 0 + _alttype="RADIO" + elseif Type==RAT.wp.air then + -- air start + _Type="Turning Point" + _Action="Turning Point" + _alttype="BARO" + elseif Type==RAT.wp.climb then + _Type="Turning Point" + _Action="Turning Point" + _alttype="BARO" + elseif Type==RAT.wp.cruise then + _Type="Turning Point" + _Action="Turning Point" + _alttype="BARO" + elseif Type==RAT.wp.descent then + _Type="Turning Point" + _Action="Turning Point" + _alttype="BARO" + elseif Type==RAT.wp.holding then + _Type="Turning Point" + _Action="Turning Point" + --_Action="Fly Over Point" + _alttype="BARO" + elseif Type==RAT.wp.landing then + _Type="Land" + _Action="Landing" + _Altitude = 0 + _alttype="RADIO" + elseif Type==RAT.wp.finalwp then + _Type="Turning Point" + --_Action="Fly Over Point" + _Action="Turning Point" + _alttype="BARO" + else + env.error("Unknown waypoint type in RAT:Waypoint() function!") + _Type="Turning Point" + _Action="Turning Point" + _alttype="RADIO" + end + + -- some debug info about input parameters + local text=string.format("\n******************************************************\n") + text=text..string.format("Waypoint = %d\n", index) + text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) + text=text..string.format("Alias = %s\n", self.alias) + text=text..string.format("Type: %i - %s\n", Type, _Type) + text=text..string.format("Action: %s\n", _Action) + text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n", Coord.x/1000, Coord.z/1000, Coord.y) + text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n", Speed, Speed*3.6, Speed*1.94384) + text=text..string.format("Land = %6.1f m ASL\n", Hland) + text=text..string.format("Altitude = %6.1f m (%s)\n", _Altitude, _alttype) + if Airport then + if Type==RAT.wp.air then + text=text..string.format("Zone = %s\n", Airport:GetName()) + else + --text=text..string.format("Airport = %s with ID %i\n", Airport:GetName(), Airport:GetID()) + text=text..string.format("Airport = %s\n", Airport:GetName()) + end + else + text=text..string.format("No airport/zone specified\n") + end + text=text.."******************************************************\n" + if self.debug then + env.info(RAT.id..text) + end + + -- define waypoint + local RoutePoint = {} + -- coordinates and altitude + RoutePoint.x = Coord.x + RoutePoint.y = Coord.z + RoutePoint.alt = _Altitude + -- altitude type: BARO=ASL or RADIO=AGL + RoutePoint.alt_type = _alttype + -- type + RoutePoint.type = _Type + RoutePoint.action = _Action + -- speed in m/s + RoutePoint.speed = Speed + RoutePoint.speed_locked = true + -- ETA (not used) + RoutePoint.ETA=nil + RoutePoint.ETA_locked = false + -- waypoint name (only for the mission editor) + RoutePoint.name="RAT waypoint" + + if (Airport~=nil) and (Type~=RAT.wp.air) then + local AirbaseID = Airport:GetID() + local AirbaseCategory = Airport:GetDesc().category + if AirbaseCategory == Airbase.Category.SHIP then + RoutePoint.linkUnit = AirbaseID + RoutePoint.helipadId = AirbaseID + --env.info(RAT.id.."WP: Ship id = "..AirbaseID) + elseif AirbaseCategory == Airbase.Category.HELIPAD then + RoutePoint.linkUnit = AirbaseID + RoutePoint.helipadId = AirbaseID + --env.info(RAT.id.."WP: Helipad id = "..AirbaseID) + elseif AirbaseCategory == Airbase.Category.AIRDROME then + RoutePoint.airdromeId = AirbaseID + --env.info(RAT.id.."WP: Airdrome id = "..AirbaseID) + else + --env.error(RAT.id.."Unknown Airport categoryin _Waypoint()!") + end + end + -- properties + RoutePoint.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + -- tasks + local TaskCombo = {} + local TaskHolding = self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, self:_Randomize(90,0.9)) + local TaskWaypoint = self:_TaskFunction("RAT._WaypointFunction", self, index) + + RoutePoint.task = {} + RoutePoint.task.id = "ComboTask" + RoutePoint.task.params = {} + + TaskCombo[#TaskCombo+1]=TaskWaypoint + if Type==RAT.wp.holding then + TaskCombo[#TaskCombo+1]=TaskHolding + end + + RoutePoint.task.params.tasks = TaskCombo + + -- Return waypoint. + return RoutePoint +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Provide information about the assigned flightplan. +-- @param #RAT self +-- @param #table waypoints Waypoints of the flight plan. +-- @param #string comment Some comment to identify the provided information. +-- @return #number total Total route length in meters. +function RAT:_Routeinfo(waypoints, comment) + local text=string.format("\n******************************************************\n") + text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) + if comment then + text=text..comment.."\n" + end + text=text..string.format("Number of waypoints = %i\n", #waypoints) + -- info on coordinate and altitude + for i=1,#waypoints do + local p=waypoints[i] + text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m %s\n", i-1, p.x/1000, p.y/1000, p.alt, self.waypointdescriptions[i]) + end + -- info on distance between waypoints + local total=0.0 + for i=1,#waypoints-1 do + local point1=waypoints[i] + local point2=waypoints[i+1] + local x1=point1.x + local y1=point1.y + local x2=point2.x + local y2=point2.y + local d=math.sqrt((x1-x2)^2 + (y1-y2)^2) + local heading=self:_Course(point1, point2) + total=total+d + text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d : %s - %s\n", i-1, i, d/1000, heading, self.waypointdescriptions[i], self.waypointdescriptions[i+1]) + end + text=text..string.format("Total distance = %6.1f km\n", total/1000) + text=text..string.format("******************************************************\n") + + -- send message + if self.debug then + --env.info(RAT.id..text) + end + env.info(RAT.id..text) + + -- return total route length in meters + return total +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Orbit at a specified position at a specified alititude with a specified speed. +-- @param #RAT self +-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position. +-- @param #number Altitude The altitude ASL at which to hold the position. +-- @param #number Speed The speed flying when holding the position in m/s. +-- @param #number Duration Duration of holding pattern in seconds. +-- @return Dcs.DCSTasking.Task#Task DCSTask +function RAT:_TaskHolding(P1, Altitude, Speed, Duration) + + --local LandHeight = land.getHeight(P1) + + --TODO: randomize P1 + -- Second point is 3 km north of P1 and 200 m for helos. + local dx=3000 + local dy=0 + if self.category==RAT.cat.heli then + dx=200 + dy=0 + end + + local P2={} + P2.x=P1.x+dx + P2.y=P1.y+dy + local Task = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + --pattern = AI.Task.OrbitPattern.CIRCLE, + point = P1, + point2 = P2, + speed = Speed, + altitude = Altitude + } + } + + local DCSTask={} + DCSTask.id="ControlledTask" + DCSTask.params={} + DCSTask.params.task=Task + + if self.ATCswitch then + -- Set stop condition for holding. Either flag=1 or after max. X min holding. + local userflagname=string.format("%s#%03d", self.alias, self.SpawnIndex+1) + local maxholdingduration=60*120 + DCSTask.params.stopCondition={userFlag=userflagname, userFlagValue=1, duration=maxholdingduration} + else + DCSTask.params.stopCondition={duration=Duration} + end + + return DCSTask +end + +--- Function which is called after passing every waypoint. Info on waypoint is given and special functions are executed. +-- @param Core.Group#GROUP group Group of aircraft. +-- @param #RAT rat RAT object. +-- @param #number wp Waypoint index. Running number of the waypoints. Determines the actions to be executed. +function RAT._WaypointFunction(group, rat, wp) + + -- Current time and Spawnindex. + local Tnow=timer.getTime() + local sdx=rat:GetSpawnIndexFromGroup(group) + + -- Departure and destination names. + local departure=rat.ratcraft[sdx].departure:GetName() + local destination=rat.ratcraft[sdx].destination:GetName() + local landing=rat.ratcraft[sdx].landing + local WPholding=rat.ratcraft[sdx].wpholding + local WPfinal=rat.ratcraft[sdx].wpfinal + + + -- For messages + local text + + -- Info on passing waypoint. + text=string.format("Flight %s passing waypoint #%d %s.", group:GetName(), wp, rat.waypointdescriptions[wp]) + env.info(RAT.id..text) + + -- New status. + local status=rat.waypointstatus[wp] + + --rat.ratcraft[sdx].status=status + rat:_SetStatus(group, status) + + if wp==WPholding then + + -- Aircraft arrived at holding point + text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.", group:GetName(), destination) + MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) + + -- Register aircraft at ATC. + if rat.ATCswitch then + MENU_MISSION_COMMAND:New("Clear for landing", rat.Menu[rat.SubMenuName].groups[sdx], rat.ClearForLanding, rat, group:GetName()) + rat:_ATCRegisterFlight(group:GetName(), Tnow) + end + end + + if wp==WPfinal then + text=string.format("Flight %s arrived at final destination %s.", group:GetName(), destination) + MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) + env.info(RAT.id..text) + + if landing==RAT.wp.air then + text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.", group:GetName()) + MESSAGE:New(text, 30):ToAllIf(rat.debug) + env.info(RAT.id..text) + -- Enable despawn switch. Next time the status function is called, the aircraft will be despawned. + rat.ratcraft[sdx].despawnme=true + end + end +end + +--- Task function. +-- @param #RAT self +-- @param #string FunctionString Name of the function to be called. +function RAT:_TaskFunction(FunctionString, ... ) + self:F2({FunctionString, arg}) + + local DCSTask + local ArgumentKey + + -- Templatename and anticipated name the group will get + local templatename=self.templategroup:GetName() + local groupname=self:_AnticipatedGroupName() + + local DCSScript = {} + --DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:FindByName(\""..groupname.."\") " + DCSScript[#DCSScript+1] = "local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " + + if arg and arg.n > 0 then + ArgumentKey = '_' .. tostring(arg):match("table: (.*)") + self.templategroup:SetState(self.templategroup, ArgumentKey, arg) + DCSScript[#DCSScript+1] = "local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '" .. ArgumentKey .. "' ) " + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" + else + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + end + + DCSTask = self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) + + --env.info(RAT.id.."Taskfunction:") + --self:E( DCSTask ) + + return DCSTask +end + +--- Anticipated group name from alias and spawn index. +-- @param #RAT self +-- @param #number index Spawnindex of group if given or self.SpawnIndex+1 by default. +-- @return #string Name the group will get after it is spawned. +function RAT:_AnticipatedGroupName(index) + local index=index or self.SpawnIndex+1 + return string.format("%s#%03d", self.alias, index) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Calculate the max flight level for a given distance and fixed climb and descent rates. +-- In other words we have a distance between two airports and want to know how high we +-- can climb before we must descent again to arrive at the destination without any level/cruising part. +-- @param #RAT self +-- @param #number alpha Angle of climb [rad]. +-- @param #number beta Angle of descent [rad]. +-- @param #number d Distance between the two airports [m]. +-- @param #number phi Angle between departure and destination [rad]. +-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible. +-- @return #number Maximal flight level in meters. +function RAT:_FLmax(alpha, beta, d, phi, h0) +-- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given. + local gamma=math.rad(180)-alpha-beta + local a=d*math.sin(alpha)/math.sin(gamma) + local b=d*math.sin(beta)/math.sin(gamma) + -- h1 and h2 should be equal. + local h1=b*math.sin(alpha) + local h2=a*math.sin(beta) + -- We also take the slope between departure and destination into account. + local h3=b*math.cos(math.pi/2-(alpha+phi)) + -- Debug message. + local text=string.format("\nFLmax = FL%3.0f = %6.1f m.\n", h1/RAT.unit.FL2m, h1) + text=text..string.format( "FLmax = FL%3.0f = %6.1f m.\n", h2/RAT.unit.FL2m, h2) + text=text..string.format( "FLmax = FL%3.0f = %6.1f m.", h3/RAT.unit.FL2m, h3) + if self.debug then + env.info(RAT.id..text) + end + return h3+h0 +end + +--- Calculate minimum distance between departure and destination for given minimum flight level and climb/decent rates. +-- @param #RAT self +-- @param #number alpha Angle of climb [rad]. +-- @param #number beta Angle of descent [rad]. +-- @param #number ha Height difference between departure and cruise altiude. +-- @param #number hb Height difference between cruise altitude and destination. +-- @return #number d1 Minimum distance for climb phase to reach cruise altitude. +-- @return #number d2 Minimum distance for descent phase to reach destination height. +-- @return #number dtot Minimum total distance to climb and descent. +function RAT:_MinDistance(alpha, beta, ha, hb) + local d1=ha/math.tan(alpha) + local d2=hb/math.tan(beta) + return d1, d2, d1+d2 +end + + +--- Add names of all friendly airports to possible departure or destination airports if they are not already in the list. +-- @param #RAT self +-- @param #table ports List of departure or destination airports/zones that will be added. +function RAT:_AddFriendlyAirports(ports) + for _,airport in pairs(self.airports) do + if not self:_NameInList(ports, airport:GetName()) then + table.insert(ports, airport:GetName()) + end + end +end + +--- Check if a name/string is in a list or not. +-- @param #RAT self +-- @param #table liste List of names to be checked. +-- @param #string name Name to be checked for. +function RAT:_NameInList(liste, name) + for _,item in pairs(liste) do + if item==name then + return true + end + end + return false +end + + +--- Test if an airport exists on the current map. +-- @param #RAT self +-- @param #string name +-- @return #boolean True if airport exsits, false otherwise. +function RAT:_AirportExists(name) + for _,airport in pairs(self.airports_map) do + if airport:GetName()==name then + return true + end + end + return false +end + +--- Test if a trigger zone defined in the mission editor exists. +-- @param #RAT self +-- @param #string name +-- @return #boolean True if zone exsits, false otherwise. +function RAT:_ZoneExists(name) + local z=trigger.misc.getZone(name) + if z then + return true + end + return false +end + +--- Set ROE for a group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group for which the ROE is set. +-- @param #string roe ROE of group. +function RAT:_SetROE(group, roe) + env.info(RAT.id.."Setting ROE to "..roe.." for group "..group:GetName()) + if self.roe==RAT.ROE.returnfire then + group:OptionROEReturnFire() + elseif self.roe==RAT.ROE.weaponfree then + group:OptionROEWeaponFree() + else + group:OptionROEHoldFire() + end +end + + +--- Set ROT for a group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group for which the ROT is set. +-- @param #string rot ROT of group. +function RAT:_SetROT(group, rot) + env.info(RAT.id.."Setting ROT to "..rot.." for group "..group:GetName()) + if self.rot==RAT.ROT.passive then + group:OptionROTPassiveDefense() + elseif self.rot==RAT.ROT.evade then + group:OptionROTEvadeFire() + else + group:OptionROTNoReaction() + end +end + + +--- Create a table with the valid coalitions for departure and destination airports. +-- @param #RAT self +function RAT:_SetCoalitionTable() + -- get all possible departures/destinations depending on coalition + if self.friendly==RAT.coal.neutral then + self.ctable={coalition.side.NEUTRAL} + elseif self.friendly==RAT.coal.same then + self.ctable={self.coalition, coalition.side.NEUTRAL} + elseif self.friendly==RAT.coal.sameonly then + self.ctable={self.coalition} + else + env.error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.") + self.ctable={self.coalition, coalition.side.NEUTRAL} + end +end + + +---Determine the heading from point a to point b. +--@param #RAT self +--@param Core.Point#COORDINATE a Point from. +--@param Core.Point#COORDINATE b Point to. +--@return #number Heading/angle in degrees. +function RAT:_Course(a,b) + local dx = b.x-a.x + -- take the right value for y-coordinate (if we have "alt" then "y" if not "z") + local ay + if a.alt then + ay=a.y + else + ay=a.z + end + local by + if b.alt then + by=b.y + else + by=b.z + end + local dy = by-ay + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + return angle +end + +---Determine the heading for an aircraft to be entered in the route template. +--@param #RAT self +--@param #number course The course between two points in degrees. +--@return #number heading Heading in rad. +function RAT:_Heading(course) + local h + if course<=180 then + h=math.rad(course) + else + h=-math.rad(360-course) + end + return h +end + + +--- Randomize a value by a certain amount. +-- @param #RAT self +-- @param #number value The value which should be randomized +-- @param #number fac Randomization factor. +-- @param #number lower (Optional) Lower limit of the returned value. +-- @param #number upper (Optional) Upper limit of the returned value. +-- @return #number Randomized value. +-- @usage _Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation. +-- @usage _Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120. +function RAT:_Randomize(value, fac, lower, upper) + local min + if lower then + min=math.max(value-value*fac, lower) + else + min=value-value*fac + end + local max + if upper then + max=math.min(value+value*fac, upper) + else + max=value+value*fac + end + + local r=math.random(min, max) + + -- debug info + if self.debug then + local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f", value, fac, min, max, r) + env.info(RAT.id..text) + end + + return r +end + +--- Generate Gaussian pseudo-random numbers. +-- @param #number x0 Expectation value of distribution. +-- @param #number sigma (Optional) Standard deviation. Default 10. +-- @param #number xmin (Optional) Lower cut-off value. +-- @param #number xmax (Optional) Upper cut-off value. +-- @return #number Gaussian random number. +function RAT:_Random_Gaussian(x0, sigma, xmin, xmax) + + -- Standard deviation. Default 10 if not given. + sigma=sigma or 10 + + local r + local gotit=false + local i=0 + while not gotit do + + -- Uniform numbers in [0,1). We need two. + local x1=math.random() + local x2=math.random() + + -- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). + r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 + + i=i+1 + if (r>=xmin and r<=xmax) or i>100 then + gotit=true + end + end + + return r + +end + +--- Place markers of the waypoints. Note we assume a very specific number and type of waypoints here. +-- @param #RAT self +-- @param #table waypoints Table with waypoints. +-- @param #number index Spawn index of group. +function RAT:_PlaceMarkers(waypoints, index) + for i=1,#waypoints do + self:_SetMarker(self.waypointdescriptions[i], waypoints[i], index) + if self.debug then + local text=string.format("Marker at waypoint #%d: %s for flight #%d", i, self.waypointdescriptions[i], index) + env.info(RAT.id..text) + end + end +end + + +--- Set a marker visible for all on the F10 map. +-- @param #RAT self +-- @param #string text Info text displayed at maker. +-- @param #table wp Position of marker coming in as waypoint, i.e. has x, y and alt components. +-- @param #number index Spawn index of group. +function RAT:_SetMarker(text, wp, index) + RAT.markerid=RAT.markerid+1 + self.markerids[#self.markerids+1]=RAT.markerid + if self.debug then + env.info(RAT.id..self.SpawnTemplatePrefix..": placing marker with ID "..RAT.markerid..": "..text) + end + -- Convert to coordinate. + local vec={x=wp.x, y=wp.alt, z=wp.y} + local flight=self:GetGroupFromIndex(index):GetName() + -- Place maker visible for all on the F10 map. + local text1=string.format("%s:\n%s", flight, text) + trigger.action.markToAll(RAT.markerid, text1, vec) +end + +--- Delete all markers on F10 map. +-- @param #RAT self +function RAT:_DeleteMarkers() + for k,v in ipairs(self.markerids) do + trigger.action.removeMark(v) + end + for k,v in ipairs(self.markerids) do + self.markerids[k]=nil + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Modifies the template of the group to be spawned. +-- In particular, the waypoints of the group's flight plan are copied into the spawn template. +-- This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug". +-- @param #RAT self +-- @param #table waypoints The waypoints of the AI flight plan. +-- @param #string livery (Optional) Livery of the aircraft. All members of a flight will get the same livery. +function RAT:_ModifySpawnTemplate(waypoints, livery) + + -- The 3D vector of the first waypoint, i.e. where we actually spawn the template group. + local PointVec3 = {x=waypoints[1].x, y=waypoints[1].alt, z=waypoints[1].y} + + -- Heading from first to seconds waypoints to align units in case of air start. + local course = self:_Course(waypoints[1], waypoints[2]) + local heading = self:_Heading(course) + + if self:_GetSpawnIndex(self.SpawnIndex+1) then + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + + if SpawnTemplate then + self:T(SpawnTemplate) + + -- Spawn aircraft in uncontolled state. + if self.uncontrolled then + SpawnTemplate.uncontrolled=true + end + + -- 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) + + -- Tranlate position. + local UnitTemplate = SpawnTemplate.units[UnitID] + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].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 + SpawnTemplate.units[UnitID].heading = heading + SpawnTemplate.units[UnitID].psi = -heading + + -- Set livery (will be the same for all units of the group). + if livery then + SpawnTemplate.units[UnitID].livery_id = livery + end + + -- Set type of aircraft. + if self.actype then + SpawnTemplate.units[UnitID]["type"] = self.actype + end + + -- Set AI skill. + SpawnTemplate.units[UnitID]["skill"] = self.skill + + -- Onboard number. + SpawnTemplate.units[UnitID]["onboard_num"] = self.SpawnIndex + + -- Modify coaltion and country of template. + SpawnTemplate.CoalitionID=self.coalition + if self.country then + SpawnTemplate.CountryID=self.country + end + + -- Parking spot. + UnitTemplate.parking = nil + UnitTemplate.parking_id = self.parking_id + --env.info(RAT.id.."Parking ID "..tostring(self.parking_id)) + + -- Initial altitude + UnitTemplate.alt=PointVec3.y + + self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + + end + + -- Copy waypoints into spawntemplate. By this we avoid the nasty DCS "landing bug" :) + for i,wp in ipairs(waypoints) do + SpawnTemplate.route.points[i]=wp + end + + -- Also modify x,y of the template. Not sure why. + SpawnTemplate.x = PointVec3.x + SpawnTemplate.y = PointVec3.z + + -- Enable/disable radio. Same as checking the COMM box in the ME + if self.radio then + SpawnTemplate.communication=self.radio + end + + -- Set radio frequency and modulation. + if self.frequency then + SpawnTemplate.frequency=self.frequency + end + if self.modulation then + SpawnTemplate.modulation=self.modulation + end + + + + -- Update modified template for spawn group. + --self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate + + self:T(SpawnTemplate) + end + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Initializes the ATC arrays and starts schedulers. +-- @param #RAT self +-- @param #table airports_map List of all airports of the map. +function RAT:_ATCInit(airports_map) + if not RAT.ATC.init then + env.info(RAT.id.."Starting RAT ATC.") + env.info(RAT.id.."Simultanious = "..RAT.ATC.Nclearance) + env.info(RAT.id.."Delay = "..RAT.ATC.delay) + RAT.ATC.init=true + for _,ap in pairs(airports_map) do + local name=ap:GetName() + RAT.ATC.airport[name]={} + RAT.ATC.airport[name].queue={} + RAT.ATC.airport[name].busy=false + RAT.ATC.airport[name].onfinal={} + RAT.ATC.airport[name].Nonfinal=0 + RAT.ATC.airport[name].traffic=0 + RAT.ATC.airport[name].Tlastclearance=nil + end + SCHEDULER:New(nil, RAT._ATCCheck, {self}, 5, 15) + SCHEDULER:New(nil, RAT._ATCStatus, {self}, 5, 60) + RAT.ATC.T0=timer.getTime() + end +end + +--- Adds andd initializes a new flight after it was spawned. +-- @param #RAT self +-- @param #string name Group name of the flight. +-- @param #string dest Name of the destination airport. +function RAT:_ATCAddFlight(name, dest) + env.info(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) + RAT.ATC.flight[name]={} + RAT.ATC.flight[name].destination=dest + RAT.ATC.flight[name].Tarrive=-1 + RAT.ATC.flight[name].holding=-1 + RAT.ATC.flight[name].Tonfinal=-1 +end + +--- Deletes a flight from ATC lists after it landed. +-- @param #RAT self +-- @param #table t Table. +-- @param #string entry Flight name which shall be deleted. +function RAT:_ATCDelFlight(t,entry) + for k,_ in pairs(t) do + if k==entry then + t[entry]=nil + end + end +end + +--- Registers a flight once it is near its holding point at the final destination. +-- @param #RAT self +-- @param #string name Group name of the flight. +-- @param #number time Time the fight first registered. +function RAT:_ATCRegisterFlight(name, time) + env.info(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.") + RAT.ATC.flight[name].Tarrive=time + RAT.ATC.flight[name].holding=0 +end + + +--- ATC status report about flights. +-- @param #RAT self +function RAT:_ATCStatus() + + -- Current time. + local Tnow=timer.getTime() + + for name,_ in pairs(RAT.ATC.flight) do + + -- Holding time at destination. + local hold=RAT.ATC.flight[name].holding + local dest=RAT.ATC.flight[name].destination + + if hold >= 0 then + + -- Some string whether the runway is busy or not. + local busy="Runway state is unknown" + if RAT.ATC.airport[dest].Nonfinal>0 then + busy="Runway is occupied by "..RAT.ATC.airport[dest].Nonfinal + else + busy="Runway is currently clear" + end + + -- Aircraft is holding. + local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) + env.info(RAT.id..text) + + elseif hold==RAT.ATC.onfinal then + + -- Aircarft is on final approach for landing. + local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal + + local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) + env.info(RAT.id..text) + + elseif hold==RAT.ATC.unregistered then + + -- Aircraft has not arrived at holding point. + --env.info(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold)) + + else + env.error(RAT.id.."Unknown holding time in RAT:_ATCStatus().") + end + end + +end + +--- Main ATC function. Updates the landing queue of all airports and inceases holding time for all flights. +-- @param #RAT self +function RAT:_ATCCheck() + + -- Init queue of flights at all airports. + RAT:_ATCQueue() + + -- Current time. + local Tnow=timer.getTime() + + for name,_ in pairs(RAT.ATC.airport) do + + for qID,flight in ipairs(RAT.ATC.airport[name].queue) do + + -- Number of aircraft in queue. + local nqueue=#RAT.ATC.airport[name].queue + + -- Conditions to clear an aircraft for landing + local landing1 + if RAT.ATC.airport[name].Tlastclearance then + -- Landing if time is enough and less then two planes are on final. + landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance > RAT.ATC.delay) and RAT.ATC.airport[name].Nonfinal < RAT.ATC.Nclearance + else + landing1=false + end + -- No other aircraft is on final. + local landing2=RAT.ATC.airport[name].Nonfinal==0 + + + if not landing1 and not landing2 then + + -- Update holding time. + RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive + + -- Debug message. + local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) + env.info(RAT.id..text) + + else + + local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) + env.info(RAT.id..text) + + -- Clear flight for landing. + RAT:_ATCClearForLanding(name, flight) + + end + + end + + end + + -- Update queue of flights at all airports. + RAT:_ATCQueue() + +end + +--- Giving landing clearance for aircraft by setting user flag. +-- @param #RAT self +-- @param #string airport Name of destination airport. +-- @param #string flight Group name of flight, which gets landing clearence. +function RAT:_ATCClearForLanding(airport, flight) + -- Flight is cleared for landing. + RAT.ATC.flight[flight].holding=RAT.ATC.onfinal + -- Airport runway is busy now. + RAT.ATC.airport[airport].busy=true + -- Flight which is landing. + RAT.ATC.airport[airport].onfinal[flight]=flight + -- Number of planes on final approach. + RAT.ATC.airport[airport].Nonfinal=RAT.ATC.airport[airport].Nonfinal+1 + -- Last time an aircraft got landing clearance. + RAT.ATC.airport[airport].Tlastclearance=timer.getTime() + -- Current time. + RAT.ATC.flight[flight].Tonfinal=timer.getTime() + -- Set user flag to 1 ==> stop condition for holding. + trigger.action.setUserFlag(flight, 1) + local flagvalue=trigger.misc.getUserFlag(flight) + + -- Debug message. + local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).", airport, flight, flagvalue) + local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight) + env.info( RAT.id..text1) + MESSAGE:New(text2, 10):ToAll() +end + +--- Takes care of organisational stuff after a plane has landed. +-- @param #RAT self +-- @param #string name Group name of flight. +function RAT:_ATCFlightLanded(name) + + if RAT.ATC.flight[name] then + + -- Destination airport. + local dest=RAT.ATC.flight[name].destination + + -- Times for holding and final approach. + local Tnow=timer.getTime() + local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal + local Thold=RAT.ATC.flight[name].Tonfinal-RAT.ATC.flight[name].Tarrive + + -- Airport is not busy any more. + RAT.ATC.airport[dest].busy=false + + -- No aircraft on final any more. + RAT.ATC.airport[dest].onfinal[name]=nil + + -- Decrease number of aircraft on final. + RAT.ATC.airport[dest].Nonfinal=RAT.ATC.airport[dest].Nonfinal-1 + + -- Remove this flight from list of flights. + RAT:_ATCDelFlight(RAT.ATC.flight, name) + + -- Increase landing counter to monitor traffic. + RAT.ATC.airport[dest].traffic=RAT.ATC.airport[dest].traffic+1 + + -- Number of planes landing per hour. + local TrafficPerHour=RAT.ATC.airport[dest].traffic/(timer.getTime()-RAT.ATC.T0)*3600 + + -- Debug info + local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60) + local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal) + local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) + local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) + env.info(RAT.id..text1) + env.info(RAT.id..text2) + env.info(RAT.id..text3) + MESSAGE:New(text4, 10):ToAll() + end + +end + +--- Creates a landing queue for all flights holding at airports. Aircraft with longest holding time gets first permission to land. +-- @param #RAT self +function RAT:_ATCQueue() + + for airport,_ in pairs(RAT.ATC.airport) do + + -- Local airport queue. + local _queue={} + + -- Loop over all flights. + for name,_ in pairs(RAT.ATC.flight) do + --fvh + local Tnow=timer.getTime() + + -- Update holding time (unless holing is set to onfinal=-100) + if RAT.ATC.flight[name].holding>=0 then + RAT.ATC.flight[name].holding=Tnow-RAT.ATC.flight[name].Tarrive + end + local hold=RAT.ATC.flight[name].holding + local dest=RAT.ATC.flight[name].destination + + -- Flight is holding at this airport. + if hold>=0 and airport==dest then + _queue[#_queue+1]={name,hold} + end + end + + -- Sort queue w.r.t holding time in ascending order. + local function compare(a,b) + return a[2] > b[2] + end + table.sort(_queue, compare) + + -- Transfer queue to airport queue. + RAT.ATC.airport[airport].queue={} + for k,v in ipairs(_queue) do + table.insert(RAT.ATC.airport[airport].queue, v[1]) + end + + --fvh + --for k,v in ipairs(RAT.ATC.airport[airport].queue) do + --print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding)) + --end + + end +end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index e6350df6e..3831475fb 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -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 diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua new file mode 100644 index 000000000..14ee3a766 --- /dev/null +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -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 + diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua new file mode 100644 index 000000000..992f26404 --- /dev/null +++ b/Moose Development/Moose/Functional/ZoneGoal.lua @@ -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 diff --git a/Moose Development/Moose/Functional/ZoneGoalCargo.lua b/Moose Development/Moose/Functional/ZoneGoalCargo.lua new file mode 100644 index 000000000..78707d461 --- /dev/null +++ b/Moose Development/Moose/Functional/ZoneGoalCargo.lua @@ -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 + diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua new file mode 100644 index 000000000..83415f3fb --- /dev/null +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -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 + diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 28688a32f..c42cea39c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -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 diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 0812459af..8e9010a3e 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -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 diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index 0c5633ea9..86117230c 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -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 diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 96e57d343..9bd94eaff 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -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 diff --git a/Moose Development/Moose/Tasking/TaskZoneCapture.lua b/Moose Development/Moose/Tasking/TaskZoneCapture.lua new file mode 100644 index 000000000..fe8afcb5e --- /dev/null +++ b/Moose Development/Moose/Tasking/TaskZoneCapture.lua @@ -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 + diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index 9adf00b9b..44687a2fb 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -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 diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index 2f03cb5e3..0167c71f9 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -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. diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index aa56d3d42..02e442d2c 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -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! diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 30cbe8f43..7613d7cad 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -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 ) diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index c6f902dc1..812691fcd 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -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 diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index cce57959b..cd54b4e1f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -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 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 58b0010b3..13ee83ab1 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -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 + diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5ee4d7da8..fc70b10e4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -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 \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 403a52a2c..90d239b75 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index f863e9bd4..cd86668c0 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -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. diff --git a/Moose Development/Moose/Wrapper/Object.lua b/Moose Development/Moose/Wrapper/Object.lua index 5e4a236f1..68ae6f775 100644 --- a/Moose Development/Moose/Wrapper/Object.lua +++ b/Moose Development/Moose/Wrapper/Object.lua @@ -79,7 +79,7 @@ function OBJECT:Destroy() local DCSObject = self:GetDCSObject() if DCSObject then - + --BASE:CreateEventCrash( timer.getTime(), DCSObject ) DCSObject:destroy() end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index fbe5421b0..d349211b0 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -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 diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index cc1e1f74c..1a76a869a 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -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 \ No newline at end of file +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 + diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 01f71e691..31eaf98ef 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -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 diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files index ca3b70fc7..c5dc29cde 100644 --- a/Moose Mission Setup/Moose.files +++ b/Moose Mission Setup/Moose.files @@ -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 diff --git a/Moose Mission Setup/Moose_.lua b/Moose Mission Setup/Moose_.lua new file mode 100644 index 000000000..f0d710d47 --- /dev/null +++ b/Moose Mission Setup/Moose_.lua @@ -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 *** ') diff --git a/Release 2.1.bb b/Release 2.1.bb deleted file mode 100644 index afa8bc203..000000000 --- a/Release 2.1.bb +++ /dev/null @@ -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 diff --git a/Release 2.1.md b/Release 2.1.md deleted file mode 100644 index d274188b2..000000000 --- a/Release 2.1.md +++ /dev/null @@ -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 diff --git a/Release Notes 2.2.0.docx b/Release Notes 2.2.0.docx new file mode 100644 index 000000000..0d018f644 Binary files /dev/null and b/Release Notes 2.2.0.docx differ diff --git a/Release Notes 2.2.0.pdf b/Release Notes 2.2.0.pdf new file mode 100644 index 000000000..6d055aa5a Binary files /dev/null and b/Release Notes 2.2.0.pdf differ diff --git a/Utils/DCS_ControlAPI.txt b/Utils/DCS_ControlAPI.txt new file mode 100644 index 000000000..a0650d801 --- /dev/null +++ b/Utils/DCS_ControlAPI.txt @@ -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 + + 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 diff --git a/Utils/Generate_Moose.bat b/Utils/Generate_Moose.bat new file mode 100644 index 000000000..ddac0d0a4 --- /dev/null +++ b/Utils/Generate_Moose.bat @@ -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 diff --git a/Utils/luarocks/lib/lua/5.1/lfs.dll b/Utils/luarocks/lib/lua/5.1/lfs.dll new file mode 100644 index 000000000..2de5164d2 Binary files /dev/null and b/Utils/luarocks/lib/lua/5.1/lfs.dll differ diff --git a/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/bin/luadocumentor b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/bin/luadocumentor new file mode 100644 index 000000000..dccc8df18 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/bin/luadocumentor @@ -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 +-- - 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') diff --git a/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/LICENSE b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/LICENSE new file mode 100644 index 000000000..11ecb7958 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/LICENSE @@ -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. diff --git a/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/README.md b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/README.md new file mode 100644 index 000000000..03611d630 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/doc/README.md @@ -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). diff --git a/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/luadocumentor-0.1.5-1.rockspec b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/luadocumentor-0.1.5-1.rockspec new file mode 100644 index 000000000..9ed686c3f --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/luadocumentor-0.1.5-1.rockspec @@ -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', + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/rock_manifest new file mode 100644 index 000000000..c286d43bd --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luadocumentor/0.1.5-1/rock_manifest @@ -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" +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/doc.css b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/doc.css new file mode 100644 index 000000000..e816a7e2c --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/doc.css @@ -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; + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/examples.html b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/examples.html new file mode 100644 index 000000000..2c1644cb8 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/examples.html @@ -0,0 +1,103 @@ + + + + LuaFileSystem + + + + + + +
+ +
+ +
LuaFileSystem
+
File System Library for the Lua Programming Language
+
+ +
+ + + +
+ +

Examples

+ +

Directory iterator

+ +

The following example iterates over a directory and recursively lists the +attributes for each file inside it.

+ +
+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 (".")
+
+ +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: examples.html,v 1.8 2007/12/14 15:28:04 carregal Exp $

+
+ +
+ + + diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/index.html b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/index.html new file mode 100644 index 000000000..2bb7f5d2c --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/index.html @@ -0,0 +1,218 @@ + + + + LuaFileSystem + + + + + + +
+ +
+ +
LuaFileSystem
+
File System Library for the Lua Programming Language
+
+ +
+ + + +
+ +

Overview

+ +

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.

+ +

LuaFileSystem is free software and uses the same +license as Lua 5.1.

+ +

Status

+ +

Current version is 1.6.3. It works with Lua 5.1, 5.2 and 5.3.

+ +

Download

+ +

LuaFileSystem source can be downloaded from its +Github +page.

+ +

History

+ +
+
Version 1.6.3 [15/Jan/2015]
+
    +
  • Lua 5.3 support.
  • +
  • Assorted bugfixes.
  • +
+ +
Version 1.6.2 [??/Oct/2012]
+
    +
  • Full Lua 5.2 compatibility (with Lua 5.1 fallbacks)
  • +
+ +
Version 1.6.1 [01/Oct/2012]
+
    +
  • fix build for Lua 5.2
  • +
+ +
Version 1.6.0 [26/Sep/2012]
+
    +
  • getcwd fix for Android
  • +
  • support for Lua 5.2
  • +
  • add lfs.link
  • +
  • other bug fixes
  • +
+ +
Version 1.5.0 [20/Oct/2009]
+
    +
  • Added explicit next and close methods to second return value of lfs.dir +(the directory object), for explicit iteration or explicit closing.
  • +
  • Added directory locking via lfs.lock_dir function (see the manual).
  • +
+
Version 1.4.2 [03/Feb/2009]
+
+
    +
  • fixed bug [#13198] + lfs.attributes(filename, 'size') overflow on files > 2 Gb again (bug report and patch by KUBO Takehiro).
  • +
  • fixed bug [#39794] + Compile error on Solaris 10 (bug report and patch by Aaron B).
  • +
  • fixed compilation problems with Borland C.
  • +
+
+ +
Version 1.4.1 [07/May/2008]
+
+
    +
  • documentation review
  • +
  • fixed Windows compilation issues
  • +
  • fixed bug in the Windows tests (patch by Shmuel Zeigerman)
  • +
  • fixed bug [#2185] + lfs.attributes(filename, 'size') overflow on files > 2 Gb +
  • +
+
+ +
Version 1.4.0 [13/Feb/2008]
+
+
    +
  • added function + lfs.setmode + (works only in Windows systems).
  • +
  • lfs.attributes + raises an error if attribute does not exist
  • +
+
+ +
Version 1.3.0 [26/Oct/2007]
+
+ +
+ +
Version 1.2.1 [08/May/2007]
+
+
    +
  • compatible only with Lua 5.1 (Lua 5.0 support was dropped)
  • +
+
+ +
Version 1.2 [15/Mar/2006]
+
+ +
+ +
Version 1.1 [30/May/2005]
+
+ +
+ +
Version 1.0 [21/Jan/2005]
+
+ +
Version 1.0 Beta [10/Nov/2004]
+
+
+ +

Credits

+ +

LuaFileSystem was designed by Roberto Ierusalimschy, +André Carregal and Tomás Guisasola as part of the +Kepler Project, +which holds its copyright. LuaFileSystem is currently maintained by Fábio Mascarenhas.

+ +

Contact us

+ +

For more information please +contact us. +Comments are welcome!

+ +

You can also reach other Kepler developers and users on the Kepler Project +mailing list.

+ +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: index.html,v 1.44 2009/02/04 21:21:33 carregal Exp $

+
+ +
+ + + diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/license.html b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/license.html new file mode 100644 index 000000000..300338172 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/license.html @@ -0,0 +1,122 @@ + + + + LuaFileSystem + + + + + + +
+ +
+ +
LuaFileSystem
+
File System Library for the Lua Programming Language
+
+ +
+ + + +
+ +

License

+ +

+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 +Open Source +software. +Its licenses are compatible with +GPL. +LuaFileSystem is not in the public domain and the +Kepler Project +keep its copyright. +The legal details are below. +

+ +

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.

+ +

The LuaFileSystem library is designed and implemented by Roberto +Ierusalimschy, André Carregal and Tomás Guisasola. +The implementation is not derived from licensed software.

+ +
+

Copyright © 2003 Kepler Project.

+ +

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:

+ +

The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software.

+ +

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.

+ +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: license.html,v 1.13 2008/02/11 22:42:21 carregal Exp $

+
+ +
+ + + diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/luafilesystem.png b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/luafilesystem.png new file mode 100644 index 000000000..e1dd8c65b Binary files /dev/null and b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/luafilesystem.png differ diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/manual.html b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/manual.html new file mode 100644 index 000000000..33c1cbea5 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/doc/us/manual.html @@ -0,0 +1,280 @@ + + + + LuaFileSystem + + + + + + +
+ +
+ +
LuaFileSystem
+
File System Library for the Lua Programming Language
+
+ +
+ + + +
+ +

Introduction

+ +

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.

+ +

Building

+ +

+LuaFileSystem should be built with Lua 5.1 so the language library +and header files for the target version must be installed properly. +

+ +

+LuaFileSystem offers a Makefile and a separate configuration file, +config, +which should be edited to suit your installation before running +make. +The file has some definitions like paths to the external libraries, +compiler options and the like. +

+ +

On Windows, the C runtime used to compile LuaFileSystem must be the same +runtime that Lua uses, or some LuaFileSystem functions will not work.

+ +

Installation

+ +

The easiest way to install LuaFileSystem is to use LuaRocks:

+ +
+luarocks install luafilesystem
+
+ +

If you prefer to install LuaFileSystem manually, the compiled binary should be copied to a directory in your +C path.

+ +

Reference

+ +

+LuaFileSystem offers the following functions: +

+ +
+
lfs.attributes (filepath [, aname])
+
Returns a table with the file attributes corresponding to + filepath (or nil 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 + lfs.attributes(filepath).aname, but the table is not created + and only one attribute is retrieved from the O.S.). + The attributes are described as follows; + attribute mode is a string, all the others are numbers, + and the time related attributes use the same time reference of + os.time: +
+
dev
+
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
+ +
ino
+
on Unix systems, this represents the inode number. On Windows systems this has no meaning
+ +
mode
+
string representing the associated protection mode (the values could be + file, directory, link, socket, + named pipe, char device, block device or + other)
+ +
nlink
+
number of hard links to the file
+ +
uid
+
user-id of owner (Unix only, always 0 on Windows)
+ +
gid
+
group-id of owner (Unix only, always 0 on Windows)
+ +
rdev
+
on Unix systems, represents the device type, for special file inodes. + On Windows systems represents the same as dev
+ +
access
+
time of last access
+ +
modification
+
time of last data modification
+ +
change
+
time of last file status change
+ +
size
+
file size, in bytes
+ +
blocks
+
block allocated for file; (Unix only)
+ +
blksize
+
optimal file system I/O blocksize; (Unix only)
+
+ This function uses stat internally thus if the given + filepath 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 + lfs.symlinkattributes. +
+ +
lfs.chdir (path)
+
Changes the current working directory to the given + path.
+ Returns true in case of success or nil plus an + error string.
+ +
lfs.lock_dir(path, [seconds_stale])
+
Creates a lockfile (called lockfile.lfs) in path 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 INT_MAX, which in practice means the lock will never + be stale. To free the the lock call lock:free().
+ 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.
+ +
lfs.currentdir ()
+
Returns a string with the current working directory or nil + plus an error string.
+ +
iter, dir_obj = lfs.dir (path)
+
+ Lua iterator over the entries of a given directory. + Each time the iterator is called with dir_obj it returns a directory entry's name as a string, or + nil if there are no more entries. You can also iterate by calling dir_obj:next(), and + explicitly close the directory before the iteration finished with dir_obj:close(). + Raises an error if path is not a directory. +
+ +
lfs.lock (filehandle, mode[, start[, length]])
+
Locks a file or a part of it. This function works on open files; the + file handle should be specified as the first argument. + The string mode could be either + r (for a read/shared lock) or w (for a + write/exclusive lock). The optional arguments start + and length can be used to specify a starting point and + its length; both should be numbers.
+ Returns true if the operation was successful; in + case of error, it returns nil plus an error string. +
+ +
lfs.link (old, new[, symlink])
+
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). +
+ +
lfs.mkdir (dirname)
+
Creates a new directory. The argument is the name of the new + directory.
+ Returns true if the operation was successful; + in case of error, it returns nil plus an error string. +
+ +
lfs.rmdir (dirname)
+
Removes an existing directory. The argument is the name of the directory.
+ Returns true if the operation was successful; + in case of error, it returns nil plus an error string.
+ +
lfs.setmode (file, mode)
+
Sets the writing mode for a file. The mode string can be either "binary" or "text". + Returns true followed the previous mode string for the file, or + nil 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 binary. +
+ +
lfs.symlinkattributes (filepath [, aname])
+
Identical to lfs.attributes 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 + lfs.attributes. +
+ +
lfs.touch (filepath [, atime [, mtime]])
+
Set access and modification times of a file. This function is + a bind to utime function. The first argument is the + filename, the second argument (atime) is the access time, + and the third argument (mtime) is the modification time. + Both times are provided in seconds (which should be generated with + Lua standard function os.time). + If the modification time is omitted, the access time provided is used; + if both times are omitted, the current time is used.
+ Returns true if the operation was successful; + in case of error, it returns nil plus an error string. +
+ +
lfs.unlock (filehandle[, start[, length]])
+
Unlocks a file or a part of it. This function works on + open files; the file handle should be specified as the first + argument. The optional arguments start and + length can be used to specify a starting point and its + length; both should be numbers.
+ Returns true if the operation was successful; + in case of error, it returns nil plus an error string. +
+
+ +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: manual.html,v 1.45 2009/06/03 20:53:55 mascarenhas Exp $

+
+ +
+ + + diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/luafilesystem-1.6.3-2.rockspec b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/luafilesystem-1.6.3-2.rockspec new file mode 100644 index 000000000..c27e2b711 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/luafilesystem-1.6.3-2.rockspec @@ -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" + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/rock_manifest new file mode 100644 index 000000000..0730f11cb --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/rock_manifest @@ -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" + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/tests/test.lua b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/tests/test.lua new file mode 100644 index 000000000..abfbd4d96 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luafilesystem/1.6.3-2/tests/test.lua @@ -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!" diff --git a/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/bin/luasrcdiet b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/bin/luasrcdiet new file mode 100644 index 000000000..28e62894a --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/bin/luasrcdiet @@ -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 specify file name to write output + -s suffix for output files (default '_') + --keep keep block comment with inside + --plugin run 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 diff --git a/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc new file mode 100644 index 000000000..345d581b5 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc @@ -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 <> 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 LuaSrcDiet’s 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. diff --git a/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc new file mode 100644 index 000000000..3ee744e7d --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc @@ -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 program’s 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=" 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 can’t 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 Lua’s 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 don’t 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. + +A’s 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 B’s activation-to-removal period is isolated from the time of A’s first access to last access, meaning B’s active time will never affect any of A’s 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 A’s 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 A’s accesses. +For no collision, all of A’s 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 isn’t 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 user’s 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._ diff --git a/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec new file mode 100644 index 000000000..eb77bef49 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec @@ -0,0 +1,41 @@ +-- vim: set ft=lua: + +package = 'LuaSrcDiet' +version = '0.3.0-2' + + source = { url = 'https://github.com/jirutka/luasrcdiet/archive/v0.3.0/luasrcdiet-0.3.0.tar.gz', md5 = 'c0ff36ef66cd0568c96bc54e9253a8fa' } + +description = { + summary = 'Compresses Lua source code by removing unnecessary characters', + detailed = [[ +This is revival of LuaSrcDiet originally written by Kein-Hong Man.]], + homepage = 'https://github.com/jirutka/luasrcdiet', + maintainer = 'Jakub Jirutka ', + license = 'MIT', +} + +dependencies = { + 'lua >= 5.1', +} + +build = { + type = 'builtin', + modules = { + ['luasrcdiet'] = 'luasrcdiet/init.lua', + ['luasrcdiet.equiv'] = 'luasrcdiet/equiv.lua', + ['luasrcdiet.fs'] = 'luasrcdiet/fs.lua', + ['luasrcdiet.llex'] = 'luasrcdiet/llex.lua', + ['luasrcdiet.lparser'] = 'luasrcdiet/lparser.lua', + ['luasrcdiet.optlex'] = 'luasrcdiet/optlex.lua', + ['luasrcdiet.optparser'] = 'luasrcdiet/optparser.lua', + ['luasrcdiet.plugin.example'] = 'luasrcdiet/plugin/example.lua', + ['luasrcdiet.plugin.html'] = 'luasrcdiet/plugin/html.lua', + ['luasrcdiet.plugin.sloc'] = 'luasrcdiet/plugin/sloc.lua', + ['luasrcdiet.utils'] = 'luasrcdiet/utils.lua', + }, + install = { + bin = { + luasrcdiet = 'bin/luasrcdiet', + } + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/rock_manifest new file mode 100644 index 000000000..2e7628412 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/luasrcdiet/0.3.0-2/rock_manifest @@ -0,0 +1,28 @@ +rock_manifest = { + bin = { + luasrcdiet = "6c318685d57f827cf5baf7037a5d6072" + }, + doc = { + ["features-and-usage.adoc"] = "157587c27a0c340d9d1dd06af9b339b5", + ["performance-stats.adoc"] = "cf5f96a86e021a3a584089fafcabd056", + ["tech-notes.adoc"] = "075bc34e667a0055e659e656baa2365a" + }, + lua = { + luasrcdiet = { + ["equiv.lua"] = "967a6b17573d229e326dbb740ad7fe8c", + ["fs.lua"] = "53db7dfc50d026b683fad68ed70ead0f", + ["init.lua"] = "c6f368e6cf311f3257067fed0fbcd06a", + ["llex.lua"] = "ede897af261fc362a82d87fbad91ea2b", + ["lparser.lua"] = "c1e1f04d412b79a040fd1c2b74112953", + ["optlex.lua"] = "7c986da991a338494c36770b4a30fa9f", + ["optparser.lua"] = "b125a271ac1c691dec68b63019b1b5da", + plugin = { + ["example.lua"] = "86b5c1e9dc7959db6b221d6d5a0db3d1", + ["html.lua"] = "c0d3336a133f0c8663f395ee98d54f6a", + ["sloc.lua"] = "fb1a91b18b701ab83f21c87733be470a" + }, + ["utils.lua"] = "bd6c1e85c6a9bf3383d336a4797fb292" + } + }, + ["luasrcdiet-0.3.0-2.rockspec"] = "da70047e1b0cbdc1ff08d060327fa110" +} diff --git a/Utils/luarocks/lib/luarocks/rocks/manifest b/Utils/luarocks/lib/luarocks/rocks/manifest new file mode 100644 index 000000000..82d24f64b --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/manifest @@ -0,0 +1,650 @@ +commands = { + luadocumentor = { + "luadocumentor/0.1.5-1" + }, + luasrcdiet = { + "luasrcdiet/0.3.0-2" + } +} +dependencies = { + luadocumentor = { + ["0.1.5-1"] = { + { + constraints = { + { + op = "~>", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + }, + { + constraints = { + { + op = "~>", + version = { + 1, 6, string = "1.6" + } + } + }, + name = "luafilesystem" + }, + { + constraints = { + { + op = "~>", + version = { + 0, 32, string = "0.32" + } + } + }, + name = "markdown" + }, + { + constraints = { + { + op = "~>", + version = { + 0, 7, string = "0.7" + } + } + }, + name = "metalua-compiler" + }, + { + constraints = { + { + op = "~>", + version = { + 0, 9, string = "0.9" + } + } + }, + name = "penlight" + } + } + }, + luafilesystem = { + ["1.6.3-2"] = { + { + constraints = { + { + op = ">=", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + } + } + }, + luasrcdiet = { + ["0.3.0-2"] = { + { + constraints = { + { + op = ">=", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + } + } + }, + markdown = { + ["0.32-2"] = { + { + constraints = { + { + op = ">=", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + } + } + }, + ["metalua-compiler"] = { + ["0.7.3-1"] = { + { + constraints = { + { + op = "~>", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + }, + { + constraints = { + { + op = "~>", + version = { + 1, 6, string = "1.6" + } + } + }, + name = "luafilesystem" + }, + { + constraints = { + { + op = ">=", + version = { + 0, 7, 3, string = "0.7.3" + } + } + }, + name = "metalua-parser" + } + } + }, + ["metalua-parser"] = { + ["0.7.3-2"] = { + { + constraints = { + { + op = ">=", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + } + } + }, + penlight = { + ["0.9.8-1"] = { + { + constraints = {}, + name = "luafilesystem" + } + } + } +} +modules = { + defaultcss = { + "luadocumentor/0.1.5-1" + }, + docgenerator = { + "luadocumentor/0.1.5-1" + }, + extractors = { + "luadocumentor/0.1.5-1" + }, + ["fs.lfs"] = { + "luadocumentor/0.1.5-1" + }, + lddextractor = { + "luadocumentor/0.1.5-1" + }, + lfs = { + "luafilesystem/1.6.3-2" + }, + luasrcdiet = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.equiv"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.fs"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.llex"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.lparser"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.optlex"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.optparser"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.plugin.example"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.plugin.html"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.plugin.sloc"] = { + "luasrcdiet/0.3.0-2" + }, + ["luasrcdiet.utils"] = { + "luasrcdiet/0.3.0-2" + }, + markdown = { + "markdown/0.32-2" + }, + ["metalua.compiler"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.bytecode"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.bytecode.compile"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.bytecode.lcode"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.bytecode.ldump"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.bytecode.lopcodes"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.globals"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.compiler.parser"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.annot.generator"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.annot.grammar"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.expr"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.ext"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.lexer"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.meta"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.misc"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.stat"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.compiler.parser.table"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.grammar.generator"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.grammar.lexer"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua.loader"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua.pprint"] = { + "metalua-parser/0.7.3-2" + }, + ["metalua/compiler/ast_to_src.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua/extension/comprehension.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua/extension/match.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua/repl.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua/treequery.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["metalua/treequery/walk.mlua"] = { + "metalua-compiler/0.7.3-1" + }, + ["models.apimodel"] = { + "luadocumentor/0.1.5-1" + }, + ["models.apimodelbuilder"] = { + "luadocumentor/0.1.5-1" + }, + ["models.internalmodel"] = { + "luadocumentor/0.1.5-1" + }, + ["models.ldparser"] = { + "luadocumentor/0.1.5-1" + }, + ["models/internalmodelbuilder.mlua"] = { + "luadocumentor/0.1.5-1" + }, + pl = { + "penlight/0.9.8-1" + }, + ["pl.Date"] = { + "penlight/0.9.8-1" + }, + ["pl.List"] = { + "penlight/0.9.8-1" + }, + ["pl.Map"] = { + "penlight/0.9.8-1" + }, + ["pl.MultiMap"] = { + "penlight/0.9.8-1" + }, + ["pl.OrderedMap"] = { + "penlight/0.9.8-1" + }, + ["pl.Set"] = { + "penlight/0.9.8-1" + }, + ["pl.app"] = { + "penlight/0.9.8-1" + }, + ["pl.array2d"] = { + "penlight/0.9.8-1" + }, + ["pl.class"] = { + "penlight/0.9.8-1" + }, + ["pl.comprehension"] = { + "penlight/0.9.8-1" + }, + ["pl.config"] = { + "penlight/0.9.8-1" + }, + ["pl.data"] = { + "penlight/0.9.8-1" + }, + ["pl.dir"] = { + "penlight/0.9.8-1" + }, + ["pl.file"] = { + "penlight/0.9.8-1" + }, + ["pl.func"] = { + "penlight/0.9.8-1" + }, + ["pl.input"] = { + "penlight/0.9.8-1" + }, + ["pl.lapp"] = { + "penlight/0.9.8-1" + }, + ["pl.lexer"] = { + "penlight/0.9.8-1" + }, + ["pl.luabalanced"] = { + "penlight/0.9.8-1" + }, + ["pl.operator"] = { + "penlight/0.9.8-1" + }, + ["pl.path"] = { + "penlight/0.9.8-1" + }, + ["pl.permute"] = { + "penlight/0.9.8-1" + }, + ["pl.platf.luajava"] = { + "penlight/0.9.8-1" + }, + ["pl.pretty"] = { + "penlight/0.9.8-1" + }, + ["pl.seq"] = { + "penlight/0.9.8-1" + }, + ["pl.sip"] = { + "penlight/0.9.8-1" + }, + ["pl.strict"] = { + "penlight/0.9.8-1" + }, + ["pl.stringio"] = { + "penlight/0.9.8-1" + }, + ["pl.stringx"] = { + "penlight/0.9.8-1" + }, + ["pl.tablex"] = { + "penlight/0.9.8-1" + }, + ["pl.template"] = { + "penlight/0.9.8-1" + }, + ["pl.test"] = { + "penlight/0.9.8-1" + }, + ["pl.text"] = { + "penlight/0.9.8-1" + }, + ["pl.utils"] = { + "penlight/0.9.8-1" + }, + ["pl.xml"] = { + "penlight/0.9.8-1" + }, + ["template.file"] = { + "luadocumentor/0.1.5-1" + }, + ["template.index"] = { + "luadocumentor/0.1.5-1" + }, + ["template.index.recordtypedef"] = { + "luadocumentor/0.1.5-1" + }, + ["template.item"] = { + "luadocumentor/0.1.5-1" + }, + ["template.page"] = { + "luadocumentor/0.1.5-1" + }, + ["template.recordtypedef"] = { + "luadocumentor/0.1.5-1" + }, + ["template.usage"] = { + "luadocumentor/0.1.5-1" + }, + ["template.utils"] = { + "luadocumentor/0.1.5-1" + }, + templateengine = { + "luadocumentor/0.1.5-1" + } +} +repository = { + luadocumentor = { + ["0.1.5-1"] = { + { + arch = "installed", + commands = { + luadocumentor = "luadocumentor" + }, + dependencies = { + luafilesystem = "1.6.3-2", + markdown = "0.32-2", + ["metalua-compiler"] = "0.7.3-1", + ["metalua-parser"] = "0.7.3-2", + penlight = "0.9.8-1" + }, + modules = { + defaultcss = "defaultcss.lua", + docgenerator = "docgenerator.lua", + extractors = "extractors.lua", + ["fs.lfs"] = "fs/lfs.lua", + lddextractor = "lddextractor.lua", + ["models.apimodel"] = "models/apimodel.lua", + ["models.apimodelbuilder"] = "models/apimodelbuilder.lua", + ["models.internalmodel"] = "models/internalmodel.lua", + ["models.ldparser"] = "models/ldparser.lua", + ["models/internalmodelbuilder.mlua"] = "models/internalmodelbuilder.mlua", + ["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", + templateengine = "templateengine.lua" + } + } + } + }, + luafilesystem = { + ["1.6.3-2"] = { + { + arch = "installed", + commands = {}, + dependencies = {}, + modules = { + lfs = "lfs.dll" + } + } + } + }, + luasrcdiet = { + ["0.3.0-2"] = { + { + arch = "installed", + commands = { + luasrcdiet = "luasrcdiet" + }, + dependencies = {}, + modules = { + luasrcdiet = "luasrcdiet/init.lua", + ["luasrcdiet.equiv"] = "luasrcdiet/equiv.lua", + ["luasrcdiet.fs"] = "luasrcdiet/fs.lua", + ["luasrcdiet.llex"] = "luasrcdiet/llex.lua", + ["luasrcdiet.lparser"] = "luasrcdiet/lparser.lua", + ["luasrcdiet.optlex"] = "luasrcdiet/optlex.lua", + ["luasrcdiet.optparser"] = "luasrcdiet/optparser.lua", + ["luasrcdiet.plugin.example"] = "luasrcdiet/plugin/example.lua", + ["luasrcdiet.plugin.html"] = "luasrcdiet/plugin/html.lua", + ["luasrcdiet.plugin.sloc"] = "luasrcdiet/plugin/sloc.lua", + ["luasrcdiet.utils"] = "luasrcdiet/utils.lua" + } + } + } + }, + markdown = { + ["0.32-2"] = { + { + arch = "installed", + commands = {}, + dependencies = {}, + modules = { + markdown = "markdown.lua" + } + } + } + }, + ["metalua-compiler"] = { + ["0.7.3-1"] = { + { + arch = "installed", + commands = {}, + dependencies = { + luafilesystem = "1.6.3-2", + ["metalua-parser"] = "0.7.3-2" + }, + modules = { + ["metalua.compiler.bytecode"] = "metalua/compiler/bytecode.lua", + ["metalua.compiler.bytecode.compile"] = "metalua/compiler/bytecode/compile.lua", + ["metalua.compiler.bytecode.lcode"] = "metalua/compiler/bytecode/lcode.lua", + ["metalua.compiler.bytecode.ldump"] = "metalua/compiler/bytecode/ldump.lua", + ["metalua.compiler.bytecode.lopcodes"] = "metalua/compiler/bytecode/lopcodes.lua", + ["metalua.compiler.globals"] = "metalua/compiler/globals.lua", + ["metalua.loader"] = "metalua/loader.lua", + ["metalua/compiler/ast_to_src.mlua"] = "metalua/compiler/ast_to_src.mlua", + ["metalua/extension/comprehension.mlua"] = "metalua/extension/comprehension.mlua", + ["metalua/extension/match.mlua"] = "metalua/extension/match.mlua", + ["metalua/repl.mlua"] = "metalua/repl.mlua", + ["metalua/treequery.mlua"] = "metalua/treequery.mlua", + ["metalua/treequery/walk.mlua"] = "metalua/treequery/walk.mlua" + } + } + } + }, + ["metalua-parser"] = { + ["0.7.3-2"] = { + { + arch = "installed", + commands = {}, + dependencies = {}, + modules = { + ["metalua.compiler"] = "metalua/compiler.lua", + ["metalua.compiler.parser"] = "metalua/compiler/parser.lua", + ["metalua.compiler.parser.annot.generator"] = "metalua/compiler/parser/annot/generator.lua", + ["metalua.compiler.parser.annot.grammar"] = "metalua/compiler/parser/annot/grammar.lua", + ["metalua.compiler.parser.expr"] = "metalua/compiler/parser/expr.lua", + ["metalua.compiler.parser.ext"] = "metalua/compiler/parser/ext.lua", + ["metalua.compiler.parser.lexer"] = "metalua/compiler/parser/lexer.lua", + ["metalua.compiler.parser.meta"] = "metalua/compiler/parser/meta.lua", + ["metalua.compiler.parser.misc"] = "metalua/compiler/parser/misc.lua", + ["metalua.compiler.parser.stat"] = "metalua/compiler/parser/stat.lua", + ["metalua.compiler.parser.table"] = "metalua/compiler/parser/table.lua", + ["metalua.grammar.generator"] = "metalua/grammar/generator.lua", + ["metalua.grammar.lexer"] = "metalua/grammar/lexer.lua", + ["metalua.pprint"] = "metalua/pprint.lua" + } + } + } + }, + penlight = { + ["0.9.8-1"] = { + { + arch = "installed", + commands = {}, + dependencies = { + luafilesystem = "1.6.3-2" + }, + modules = { + pl = "pl/init.lua", + ["pl.Date"] = "pl/Date.lua", + ["pl.List"] = "pl/List.lua", + ["pl.Map"] = "pl/Map.lua", + ["pl.MultiMap"] = "pl/MultiMap.lua", + ["pl.OrderedMap"] = "pl/OrderedMap.lua", + ["pl.Set"] = "pl/Set.lua", + ["pl.app"] = "pl/app.lua", + ["pl.array2d"] = "pl/array2d.lua", + ["pl.class"] = "pl/class.lua", + ["pl.comprehension"] = "pl/comprehension.lua", + ["pl.config"] = "pl/config.lua", + ["pl.data"] = "pl/data.lua", + ["pl.dir"] = "pl/dir.lua", + ["pl.file"] = "pl/file.lua", + ["pl.func"] = "pl/func.lua", + ["pl.input"] = "pl/input.lua", + ["pl.lapp"] = "pl/lapp.lua", + ["pl.lexer"] = "pl/lexer.lua", + ["pl.luabalanced"] = "pl/luabalanced.lua", + ["pl.operator"] = "pl/operator.lua", + ["pl.path"] = "pl/path.lua", + ["pl.permute"] = "pl/permute.lua", + ["pl.platf.luajava"] = "pl/platf/luajava.lua", + ["pl.pretty"] = "pl/pretty.lua", + ["pl.seq"] = "pl/seq.lua", + ["pl.sip"] = "pl/sip.lua", + ["pl.strict"] = "pl/strict.lua", + ["pl.stringio"] = "pl/stringio.lua", + ["pl.stringx"] = "pl/stringx.lua", + ["pl.tablex"] = "pl/tablex.lua", + ["pl.template"] = "pl/template.lua", + ["pl.test"] = "pl/test.lua", + ["pl.text"] = "pl/text.lua", + ["pl.utils"] = "pl/utils.lua", + ["pl.xml"] = "pl/xml.lua" + } + } + } + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/markdown-0.32-2.rockspec b/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/markdown-0.32-2.rockspec new file mode 100644 index 000000000..abbfc89e1 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/markdown-0.32-2.rockspec @@ -0,0 +1,23 @@ +package = "Markdown" +version = "0.32-2" +source = { + url = "http://www.frykholm.se/files/markdown-0.32.tar.gz", + dir = "." +} +description = { + summary = "Markdown text-to-html markup system.", + detailed = [[ + A pure-lua implementation of the Markdown text-to-html markup system. + ]], + license = "MIT", + homepage = "http://www.frykholm.se/files/markdown.lua" +} +dependencies = { + "lua >= 5.1", +} +build = { + type = "none", + install = { + lua = { "markdown.lua" }, + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/rock_manifest new file mode 100644 index 000000000..8f3d633ca --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/markdown/0.32-2/rock_manifest @@ -0,0 +1,6 @@ +rock_manifest = { + lua = { + ["markdown.lua"] = "0ea5f9d6d22a6c9aa4fdf63cf1d7d066" + }, + ["markdown-0.32-2.rockspec"] = "83f0335058d8fbd078d4f2c1ce941df0" +} diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-compiler.md b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-compiler.md new file mode 100644 index 000000000..b2679cdb5 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-compiler.md @@ -0,0 +1,104 @@ +Metalua Compiler +================ + +## Metalua compiler + +This module `metalua-compiler` depends on `metalua-parser`. Its main +feature is to compile ASTs into Lua 5.1 bytecode, allowing to convert +them into bytecode files and executable functions. This opens the +following possibilities: + +* compiler objects generated with `require 'metalua.compiler'.new()` + support methods `:xxx_to_function()` and `:xxx_to_bytecode()`; + +* Compile-time meta-programming: use of `-{...}` splices in source + code, to generate code during compilation; + +* Some syntax extensions, such as structural pattern matching and + lists by comprehension; + +* Some AST manipulation facilities such as `treequery`, which are + implemented with Metalua syntax extensions. + +## What's new in Metalua 0.7 + +This is a major overhaul of the compiler's architecture. Some of the +most noteworthy changes are: + +* No more installation or bootstrap script. Some Metalua source files + have been rewritten in plain Lua, and module sources have been + refactored, so that if you just drop the `metalua` folder somewhere + in your `LUA_PATH`, it works. + +* The compiler can be cut in two parts: + + * a parser which generates ASTs out of Lua sources, and should be + either portable or easily ported to Lua 5.2; + + * a compiler, which can turn sources and AST into executable + Lua 5.1 bytecode and run it. It also supports compile-time + meta-programming, i.e. code included between `-{ ... }` is + executed during compilation, and the ASTs it produces are + included in the resulting bytecode. + +* Both parts are packaged as separate LuaRocks, `metalua-parser` and + `metalua-compiler` respectively, so that you can install the former + without the latter. + +* The parser is not a unique object anymore. Instead, + `require "metalua.compiler".new()` returns a different compiler + instance every time it's called. Compiler instances can be reused on + as many source files as wanted, but extending one instance's grammar + doesn't affect other compiler instances. + +* Included standard library has been shed. There are too many standard + libs in Lua, and none of them is standard enough, offering + yet-another-one, coupled with a specific compiler can only add to + confusion. + +* Many syntax extensions, which either were arguably more code samples + than actual production-ready tools, or relied too heavily on the + removed runtime standard libraries, have been removed. + +* The remaining libraries and samples are: + + * `metalua.compiler` converts sources into ASTs, bytecode, + functions, and ASTs back into sources. + + * `metalua` compiles and/or executes files from the command line, + can start an interactive REPL session. + + * `metalua.loader` adds a package loader which allows to use modules + written in Metalua, even from a plain Lua program. + + * `metalua.treequery` is an advanced DSL allowing to search ASTs in + a smart way, e.g. "_search `return` statements which return a + `local` variable but aren't in a nested `function`_". + + * `metalua.extension.comprehension` is a language extension which + supports lists by comprehension + (`even = { i for i=1, 100 if i%2==0 }`) and improved loops + (`for i=1, 10 for j=1,10 if i~=j do print(i,j) end`). + + * `metalua.extension.match` is a language extension which offers + Haskell/ML structural pattern matching + (``match AST with `Function{ args, body } -> ... | `Number{ 0 } -> ...end``) + + * **TODO Move basic extensions in a separate module.** + +* To remove the compilation speed penalty associated with + metaprogramming, when environment variable `LUA_MCACHE` or Lua + variable `package.mcache` is defined and LuaFileSystem is available, + the results of Metalua source compilations is cached. Unless the + source file is more recent than the latest cached bytecode file, the + latter is loaded instead of the former. + +* The Luarock install for the full compiler lists dependencies towards + Readline, LuaFileSytem, and Alt-Getopts. Those projects are + optional, but having them automatically installed by LuaRocks offers + a better user experience. + +* The license has changed from MIT to double license MIT + EPL. This + has been done in order to provide the IP guarantees expected by the + Eclipse Foundation, to include Metalua in Eclipse's + [Lua Development Tools](http://www.eclipse.org/koneki/ldt/). diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-parser.md b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-parser.md new file mode 100644 index 000000000..98e34ee43 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/doc/README-parser.md @@ -0,0 +1,177 @@ +Metalua Parser +============== + +`metalua-parser` is a subset of the Metalua compiler, which turns +valid Lua source files and strings into abstract syntax trees +(AST). This README includes a description of this AST format. People +interested by Lua code analysis and generation are encouraged to +produce and/or consume this format to represent ASTs. + +It has been designed for Lua 5.1. It hasn't been tested against +Lua 5.2, but should be easily ported. + +## Usage + +Module `metalua.compiler` has a `new()` function, which returns a +compiler instance. This instance has a set of methods of the form +`:xxx_to_yyy(input)`, where `xxx` and `yyy` must be one of the +following: + +* `srcfile` the name of a Lua source file; +* `src` a string containing the Lua sources of a list of statements; +* `lexstream` a lexical tokens stream; +* `ast` an abstract syntax tree; +* `bytecode` a chunk of Lua bytecode that can be loaded in a Lua 5.1 + VM (not available if you only installed the parser); +* `function` an executable Lua function. + +Compiling into bytecode or executable functions requires the whole +Metalua compiler, not only the parser. The most frequently used +functions are `:src_to_ast(source_string)` and +`:srcfile_to_ast("path/to/source/file.lua")`. + + mlc = require 'metalua.compiler'.new() + ast = mlc :src_to_ast[[ return 123 ]] + +A compiler instance can be reused as much as you want; it's only +interesting to work with more than one compiler instance when you +start extending their grammars. + +## Abstract Syntax Trees definition + +### Notation + +Trees are written below with some Metalua syntax sugar, which +increases their readability. the backquote symbol introduces a `tag`, +i.e. a string stored in the `"tag"` field of a table: + +* `` `Foo{ 1, 2, 3 }`` is a shortcut for `{tag="Foo", 1, 2, 3}`; +* `` `Foo`` is a shortcut for `{tag="Foo"}`; +* `` `Foo 123`` is a shortcut for `` `Foo{ 123 }``, and therefore + `{tag="Foo", 123 }`; the expression after the tag must be a literal + number or string. + +When using a Metalua interpreter or compiler, the backtick syntax is +supported and can be used directly. Metalua's pretty-printing helpers +also try to use backtick syntax whenever applicable. + +### Tree elements + +Tree elements are mainly categorized into statements `stat`, +expressions `expr` and lists of statements `block`. Auxiliary +definitions include function applications/method invocation `apply`, +are both valid statements and expressions, expressions admissible on +the left-hand-side of an assignment statement `lhs`. + + block: { stat* } + + stat: + `Do{ stat* } + | `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2... + | `While{ expr block } -- while e do b end + | `Repeat{ block expr } -- repeat b until e + | `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end + | `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end + | `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end + | `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2... + | `Localrec{ ident expr } -- only used for 'local function' + | `Goto{ } -- goto str + | `Label{ } -- ::str:: + | `Return{ } -- return e1, e2... + | `Break -- break + | apply + + expr: + `Nil | `Dots | `True | `False + | `Number{ } + | `String{ } + | `Function{ { ident* `Dots? } block } + | `Table{ ( `Pair{ expr expr } | expr )* } + | `Op{ opid expr expr? } + | `Paren{ expr } -- significant to cut multiple values returns + | apply + | lhs + + apply: + `Call{ expr expr* } + | `Invoke{ expr `String{ } expr* } + + ident: `Id{ } + + lhs: ident | `Index{ expr expr } + + opid: 'add' | 'sub' | 'mul' | 'div' + | 'mod' | 'pow' | 'concat'| 'eq' + | 'lt' | 'le' | 'and' | 'or' + | 'not' | 'len' + +### Meta-data (lineinfo) + + +ASTs also embed some metadata, allowing to map them to their source +representation. Those informations are stored in a `"lineinfo"` field +in each tree node, which points to the range of characters in the +source string which represents it, and to the content of any comment +that would appear immediately before or after that node. + +Lineinfo objects have two fields, `"first"` and `"last"`, describing +respectively the beginning and the end of the subtree in the +sources. For instance, the sub-node ``Number{123}` produced by parsing +`[[return 123]]` will have `lineinfo.first` describing offset 8, and +`lineinfo.last` describing offset 10: + + + > mlc = require 'metalua.compiler'.new() + > ast = mlc :src_to_ast "return 123 -- comment" + > print(ast[1][1].lineinfo) + + > + +A lineinfo keeps track of character offsets relative to the beginning +of the source string/file ("K8-10" above), line numbers (L1 above; a +lineinfo spanning on several lines would read something like "L1-10"), +columns i.e. offset within the line ("C8-10" above), and a filename if +available (the "?" mark above indicating that we have no file name, as +the AST comes from a string). The final "|C>" indicates that there's a +comment immediately after the node; an initial " 5.1", -- Lua 5.2 bytecode not supported + "luafilesystem ~> 1.6", -- Cached compilation based on file timestamps + "metalua-parser >= 0.7.3", -- AST production +} + +build = { + type="builtin", + modules={ + ["metalua.compiler.bytecode"] = "metalua/compiler/bytecode.lua", + ["metalua.compiler.globals"] = "metalua/compiler/globals.lua", + ["metalua.compiler.bytecode.compile"] = "metalua/compiler/bytecode/compile.lua", + ["metalua.compiler.bytecode.lcode"] = "metalua/compiler/bytecode/lcode.lua", + ["metalua.compiler.bytecode.lopcodes"] = "metalua/compiler/bytecode/lopcodes.lua", + ["metalua.compiler.bytecode.ldump"] = "metalua/compiler/bytecode/ldump.lua", + ["metalua.loader"] = "metalua/loader.lua", + }, + install={ + lua={ + ["metalua.treequery"] = "metalua/treequery.mlua", + ["metalua.compiler.ast_to_src"] = "metalua/compiler/ast_to_src.mlua", + ["metalua.treequery.walk"] = "metalua/treequery/walk.mlua", + ["metalua.extension.match"] = "metalua/extension/match.mlua", + ["metalua.extension.comprehension"] = "metalua/extension/comprehension.mlua", + ["metalua.repl"] = "metalua/repl.mlua", + } + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/rock_manifest new file mode 100644 index 000000000..7452a8f94 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-compiler/0.7.3-1/rock_manifest @@ -0,0 +1,33 @@ +rock_manifest = { + doc = { + ["README-compiler.md"] = "292523d759247d210d32fb2f6153e0f4", + ["README-parser.md"] = "b44e3673d96dd296f2c0e92a6c87ee18", + ["README.md"] = "20bfb490cddef9e101e44688791abcda" + }, + lua = { + metalua = { + compiler = { + ["ast_to_src.mlua"] = "1309f76df37585ef8e1f67f748b07b22", + bytecode = { + ["compile.lua"] = "430e4a6fac8b64b5ebb3ae585ebae75a", + ["lcode.lua"] = "3ad8755ebe8ea8eca6b1d2846eec92c4", + ["ldump.lua"] = "295e1d9657fb0126ce3471b3366da694", + ["lopcodes.lua"] = "a0f15cfc93b026b0a868466d066f1d21" + }, + ["bytecode.lua"] = "1032e5233455fd4e504daf5d2893527b", + ["globals.lua"] = "80ae19c6e640de0746348c91633c4c55" + }, + extension = { + ["comprehension.mlua"] = "426f5856896bda4c3763bd5f61410685", + ["match.mlua"] = "79960265331e8b2f46199c2411a103de" + }, + ["loader.lua"] = "1cdbf6cdf6ca97c55540d068474f1d8a", + ["repl.mlua"] = "729456f3a8cc073788acee564a0495f0", + treequery = { + ["walk.mlua"] = "5159aaddbec55936f91ea4236f6451d3" + }, + ["treequery.mlua"] = "97ffcee0825ac3bc776d01566767b2e8" + } + }, + ["metalua-compiler-0.7.3-1.rockspec"] = "b3883b25641d862db6828300bb755d51" +} diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-compiler.md b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-compiler.md new file mode 100644 index 000000000..b2679cdb5 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-compiler.md @@ -0,0 +1,104 @@ +Metalua Compiler +================ + +## Metalua compiler + +This module `metalua-compiler` depends on `metalua-parser`. Its main +feature is to compile ASTs into Lua 5.1 bytecode, allowing to convert +them into bytecode files and executable functions. This opens the +following possibilities: + +* compiler objects generated with `require 'metalua.compiler'.new()` + support methods `:xxx_to_function()` and `:xxx_to_bytecode()`; + +* Compile-time meta-programming: use of `-{...}` splices in source + code, to generate code during compilation; + +* Some syntax extensions, such as structural pattern matching and + lists by comprehension; + +* Some AST manipulation facilities such as `treequery`, which are + implemented with Metalua syntax extensions. + +## What's new in Metalua 0.7 + +This is a major overhaul of the compiler's architecture. Some of the +most noteworthy changes are: + +* No more installation or bootstrap script. Some Metalua source files + have been rewritten in plain Lua, and module sources have been + refactored, so that if you just drop the `metalua` folder somewhere + in your `LUA_PATH`, it works. + +* The compiler can be cut in two parts: + + * a parser which generates ASTs out of Lua sources, and should be + either portable or easily ported to Lua 5.2; + + * a compiler, which can turn sources and AST into executable + Lua 5.1 bytecode and run it. It also supports compile-time + meta-programming, i.e. code included between `-{ ... }` is + executed during compilation, and the ASTs it produces are + included in the resulting bytecode. + +* Both parts are packaged as separate LuaRocks, `metalua-parser` and + `metalua-compiler` respectively, so that you can install the former + without the latter. + +* The parser is not a unique object anymore. Instead, + `require "metalua.compiler".new()` returns a different compiler + instance every time it's called. Compiler instances can be reused on + as many source files as wanted, but extending one instance's grammar + doesn't affect other compiler instances. + +* Included standard library has been shed. There are too many standard + libs in Lua, and none of them is standard enough, offering + yet-another-one, coupled with a specific compiler can only add to + confusion. + +* Many syntax extensions, which either were arguably more code samples + than actual production-ready tools, or relied too heavily on the + removed runtime standard libraries, have been removed. + +* The remaining libraries and samples are: + + * `metalua.compiler` converts sources into ASTs, bytecode, + functions, and ASTs back into sources. + + * `metalua` compiles and/or executes files from the command line, + can start an interactive REPL session. + + * `metalua.loader` adds a package loader which allows to use modules + written in Metalua, even from a plain Lua program. + + * `metalua.treequery` is an advanced DSL allowing to search ASTs in + a smart way, e.g. "_search `return` statements which return a + `local` variable but aren't in a nested `function`_". + + * `metalua.extension.comprehension` is a language extension which + supports lists by comprehension + (`even = { i for i=1, 100 if i%2==0 }`) and improved loops + (`for i=1, 10 for j=1,10 if i~=j do print(i,j) end`). + + * `metalua.extension.match` is a language extension which offers + Haskell/ML structural pattern matching + (``match AST with `Function{ args, body } -> ... | `Number{ 0 } -> ...end``) + + * **TODO Move basic extensions in a separate module.** + +* To remove the compilation speed penalty associated with + metaprogramming, when environment variable `LUA_MCACHE` or Lua + variable `package.mcache` is defined and LuaFileSystem is available, + the results of Metalua source compilations is cached. Unless the + source file is more recent than the latest cached bytecode file, the + latter is loaded instead of the former. + +* The Luarock install for the full compiler lists dependencies towards + Readline, LuaFileSytem, and Alt-Getopts. Those projects are + optional, but having them automatically installed by LuaRocks offers + a better user experience. + +* The license has changed from MIT to double license MIT + EPL. This + has been done in order to provide the IP guarantees expected by the + Eclipse Foundation, to include Metalua in Eclipse's + [Lua Development Tools](http://www.eclipse.org/koneki/ldt/). diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-parser.md b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-parser.md new file mode 100644 index 000000000..98e34ee43 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/doc/README-parser.md @@ -0,0 +1,177 @@ +Metalua Parser +============== + +`metalua-parser` is a subset of the Metalua compiler, which turns +valid Lua source files and strings into abstract syntax trees +(AST). This README includes a description of this AST format. People +interested by Lua code analysis and generation are encouraged to +produce and/or consume this format to represent ASTs. + +It has been designed for Lua 5.1. It hasn't been tested against +Lua 5.2, but should be easily ported. + +## Usage + +Module `metalua.compiler` has a `new()` function, which returns a +compiler instance. This instance has a set of methods of the form +`:xxx_to_yyy(input)`, where `xxx` and `yyy` must be one of the +following: + +* `srcfile` the name of a Lua source file; +* `src` a string containing the Lua sources of a list of statements; +* `lexstream` a lexical tokens stream; +* `ast` an abstract syntax tree; +* `bytecode` a chunk of Lua bytecode that can be loaded in a Lua 5.1 + VM (not available if you only installed the parser); +* `function` an executable Lua function. + +Compiling into bytecode or executable functions requires the whole +Metalua compiler, not only the parser. The most frequently used +functions are `:src_to_ast(source_string)` and +`:srcfile_to_ast("path/to/source/file.lua")`. + + mlc = require 'metalua.compiler'.new() + ast = mlc :src_to_ast[[ return 123 ]] + +A compiler instance can be reused as much as you want; it's only +interesting to work with more than one compiler instance when you +start extending their grammars. + +## Abstract Syntax Trees definition + +### Notation + +Trees are written below with some Metalua syntax sugar, which +increases their readability. the backquote symbol introduces a `tag`, +i.e. a string stored in the `"tag"` field of a table: + +* `` `Foo{ 1, 2, 3 }`` is a shortcut for `{tag="Foo", 1, 2, 3}`; +* `` `Foo`` is a shortcut for `{tag="Foo"}`; +* `` `Foo 123`` is a shortcut for `` `Foo{ 123 }``, and therefore + `{tag="Foo", 123 }`; the expression after the tag must be a literal + number or string. + +When using a Metalua interpreter or compiler, the backtick syntax is +supported and can be used directly. Metalua's pretty-printing helpers +also try to use backtick syntax whenever applicable. + +### Tree elements + +Tree elements are mainly categorized into statements `stat`, +expressions `expr` and lists of statements `block`. Auxiliary +definitions include function applications/method invocation `apply`, +are both valid statements and expressions, expressions admissible on +the left-hand-side of an assignment statement `lhs`. + + block: { stat* } + + stat: + `Do{ stat* } + | `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2... + | `While{ expr block } -- while e do b end + | `Repeat{ block expr } -- repeat b until e + | `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end + | `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end + | `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end + | `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2... + | `Localrec{ ident expr } -- only used for 'local function' + | `Goto{ } -- goto str + | `Label{ } -- ::str:: + | `Return{ } -- return e1, e2... + | `Break -- break + | apply + + expr: + `Nil | `Dots | `True | `False + | `Number{ } + | `String{ } + | `Function{ { ident* `Dots? } block } + | `Table{ ( `Pair{ expr expr } | expr )* } + | `Op{ opid expr expr? } + | `Paren{ expr } -- significant to cut multiple values returns + | apply + | lhs + + apply: + `Call{ expr expr* } + | `Invoke{ expr `String{ } expr* } + + ident: `Id{ } + + lhs: ident | `Index{ expr expr } + + opid: 'add' | 'sub' | 'mul' | 'div' + | 'mod' | 'pow' | 'concat'| 'eq' + | 'lt' | 'le' | 'and' | 'or' + | 'not' | 'len' + +### Meta-data (lineinfo) + + +ASTs also embed some metadata, allowing to map them to their source +representation. Those informations are stored in a `"lineinfo"` field +in each tree node, which points to the range of characters in the +source string which represents it, and to the content of any comment +that would appear immediately before or after that node. + +Lineinfo objects have two fields, `"first"` and `"last"`, describing +respectively the beginning and the end of the subtree in the +sources. For instance, the sub-node ``Number{123}` produced by parsing +`[[return 123]]` will have `lineinfo.first` describing offset 8, and +`lineinfo.last` describing offset 10: + + + > mlc = require 'metalua.compiler'.new() + > ast = mlc :src_to_ast "return 123 -- comment" + > print(ast[1][1].lineinfo) + + > + +A lineinfo keeps track of character offsets relative to the beginning +of the source string/file ("K8-10" above), line numbers (L1 above; a +lineinfo spanning on several lines would read something like "L1-10"), +columns i.e. offset within the line ("C8-10" above), and a filename if +available (the "?" mark above indicating that we have no file name, as +the AST comes from a string). The final "|C>" indicates that there's a +comment immediately after the node; an initial "= 5.1" +} +build = { + type="builtin", + modules={ + ["metalua.grammar.generator"] = "metalua/grammar/generator.lua", + ["metalua.grammar.lexer"] = "metalua/grammar/lexer.lua", + ["metalua.compiler.parser"] = "metalua/compiler/parser.lua", + ["metalua.compiler.parser.table"] = "metalua/compiler/parser/table.lua", + ["metalua.compiler.parser.ext"] = "metalua/compiler/parser/ext.lua", + ["metalua.compiler.parser.annot.generator"] = "metalua/compiler/parser/annot/generator.lua", + ["metalua.compiler.parser.annot.grammar"] = "metalua/compiler/parser/annot/grammar.lua", + ["metalua.compiler.parser.stat"] = "metalua/compiler/parser/stat.lua", + ["metalua.compiler.parser.misc"] = "metalua/compiler/parser/misc.lua", + ["metalua.compiler.parser.lexer"] = "metalua/compiler/parser/lexer.lua", + ["metalua.compiler.parser.meta"] = "metalua/compiler/parser/meta.lua", + ["metalua.compiler.parser.expr"] = "metalua/compiler/parser/expr.lua", + ["metalua.compiler"] = "metalua/compiler.lua", + ["metalua.pprint"] = "metalua/pprint.lua", + } +} diff --git a/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/rock_manifest new file mode 100644 index 000000000..4640e3d33 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/metalua-parser/0.7.3-2/rock_manifest @@ -0,0 +1,34 @@ +rock_manifest = { + doc = { + ["README-compiler.md"] = "292523d759247d210d32fb2f6153e0f4", + ["README-parser.md"] = "b44e3673d96dd296f2c0e92a6c87ee18", + ["README.md"] = "20bfb490cddef9e101e44688791abcda" + }, + lua = { + metalua = { + compiler = { + parser = { + annot = { + ["generator.lua"] = "d86f7507d66ba6a3692a6f8611e9939b", + ["grammar.lua"] = "7d195bde7992efd9923771751b67b18f" + }, + ["expr.lua"] = "3a0b1984a6f92280e2e63b074fdcec10", + ["ext.lua"] = "a99e31a07bc390b826f6653bcc47d89b", + ["lexer.lua"] = "eac0f9d475d9dae4ea5a2724014cebec", + ["meta.lua"] = "12870bceda6395695020b739196e2a92", + ["misc.lua"] = "49d59f4fc1bfb77b36f78d4f87ae258f", + ["stat.lua"] = "83f10ac899be12ca4df58bbe8645299f", + ["table.lua"] = "5d2389e89603b7f78c731e6918aa1a9b" + }, + ["parser.lua"] = "e6ae68ce200de8071bb0fefad97f9b79" + }, + ["compiler.lua"] = "ca65ee9a3053581f4315821a31d0c1fd", + grammar = { + ["generator.lua"] = "b8a29e817d6798c12f40a230a0f6d0af", + ["lexer.lua"] = "7cb7c835479a9be884130eaacb9be60a" + }, + ["pprint.lua"] = "0b9bd8757b45c2d4be30106abcbd45b2" + } + }, + ["metalua-parser-0.7.3-2.rockspec"] = "a56680900b0b51701db7cd7abf49af92" +} diff --git a/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/penlight-0.9.8-1.rockspec b/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/penlight-0.9.8-1.rockspec new file mode 100644 index 000000000..d1d8b0f85 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/penlight-0.9.8-1.rockspec @@ -0,0 +1,66 @@ +package = "penlight" +version = "0.9.8-1" + +source = { + dir = "penlight-0.9.8", + url = "http://stevedonovan.github.com/files/penlight-0.9.8-core.zip", +} + +description = { + summary = "Lua utility libraries loosely based on the Python standard libraries", + homepage = "http://stevedonovan.github.com/Penlight", + license = "MIT/X11", + maintainer = "steve.j.donovan@gmail.com", + detailed = [[ +Penlight is a set of pure Lua libraries for making it easier to work with common tasks like +iterating over directories, reading configuration files and the like. Provides functional operations +on tables and sequences. +]] +} + +dependencies = { + "luafilesystem", +} + +build = { + type = "builtin", + modules = { + ["pl.strict"] = "lua/pl/strict.lua", + ["pl.dir"] = "lua/pl/dir.lua", + ["pl.operator"] = "lua/pl/operator.lua", + ["pl.input"] = "lua/pl/input.lua", + ["pl.config"] = "lua/pl/config.lua", + ["pl.seq"] = "lua/pl/seq.lua", + ["pl.stringio"] = "lua/pl/stringio.lua", + ["pl.text"] = "lua/pl/text.lua", + ["pl.test"] = "lua/pl/test.lua", + ["pl.tablex"] = "lua/pl/tablex.lua", + ["pl.app"] = "lua/pl/app.lua", + ["pl.stringx"] = "lua/pl/stringx.lua", + ["pl.lexer"] = "lua/pl/lexer.lua", + ["pl.utils"] = "lua/pl/utils.lua", + ["pl.sip"] = "lua/pl/sip.lua", + ["pl.permute"] = "lua/pl/permute.lua", + ["pl.pretty"] = "lua/pl/pretty.lua", + ["pl.class"] = "lua/pl/class.lua", + ["pl.List"] = "lua/pl/List.lua", + ["pl.data"] = "lua/pl/data.lua", + ["pl.Date"] = "lua/pl/Date.lua", + ["pl"] = "lua/pl/init.lua", + ["pl.luabalanced"] = "lua/pl/luabalanced.lua", + ["pl.comprehension"] = "lua/pl/comprehension.lua", + ["pl.path"] = "lua/pl/path.lua", + ["pl.array2d"] = "lua/pl/array2d.lua", + ["pl.func"] = "lua/pl/func.lua", + ["pl.lapp"] = "lua/pl/lapp.lua", + ["pl.file"] = "lua/pl/file.lua", + ['pl.template'] = "lua/pl/template.lua", + ["pl.Map"] = "lua/pl/Map.lua", + ["pl.MultiMap"] = "lua/pl/MultiMap.lua", + ["pl.OrderedMap"] = "lua/pl/OrderedMap.lua", + ["pl.Set"] = "lua/pl/Set.lua", + ["pl.xml"] = "lua/pl/xml.lua", + ["pl.platf.luajava"] = "lua/pl/platf/luajava.lua" + }, +} + diff --git a/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/rock_manifest b/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/rock_manifest new file mode 100644 index 000000000..8565ebbd3 --- /dev/null +++ b/Utils/luarocks/lib/luarocks/rocks/penlight/0.9.8-1/rock_manifest @@ -0,0 +1,45 @@ +rock_manifest = { + lua = { + pl = { + ["Date.lua"] = "d2131d59151ce978c4db6a648fcd275a", + ["List.lua"] = "1236c5eb08956619daacd25a462a9682", + ["Map.lua"] = "0297a536ac0595ac59e8828f8c867f53", + ["MultiMap.lua"] = "e5f898fe2443e51c38825e9bc3d1aee5", + ["OrderedMap.lua"] = "bd8e39c59e22c582a33e2f025d3ae914", + ["Set.lua"] = "346ff7392fd4aeda418fb832e8da7a7f", + ["app.lua"] = "23ffb79e69a3fd679013cf82d95ed792", + ["array2d.lua"] = "77618ec2e2de4d6d237484dfd742cd73", + ["class.lua"] = "6f58bf39e7f90711b6840ad6955d258e", + ["comprehension.lua"] = "f8600ba945dde5d959194500a687c69f", + ["config.lua"] = "9ea3ce0ac3cdf2ce0e17f1353f32abb6", + ["data.lua"] = "be446ff813b5bcf30b4063601165df6a", + ["dir.lua"] = "3d60d4c1caeaabe199fe361e4e9b14a4", + ["file.lua"] = "f5c9527ea14b511d2cb9af80b219c562", + ["func.lua"] = "cc50d73512b6d0518f6587b82844de8c", + ["init.lua"] = "9232be7d8790d4f907972a00dec7949d", + ["input.lua"] = "bab7c64ca9a740df5e9fb9909610bbc4", + ["lapp.lua"] = "1cc81f048bc3fcd775c40cd9a2d601a7", + ["lexer.lua"] = "da0db5e323a2d37545ccb02592d0d3c8", + ["luabalanced.lua"] = "00b94a997a9ea4d73f54c10893f3b35f", + ["operator.lua"] = "e606629c738966cf497bb938457adebd", + ["path.lua"] = "b0714bc337c068b7252f64250fe59604", + ["permute.lua"] = "b0ed9ba2787119ef99468329a54ea16a", + platf = { + ["luajava.lua"] = "9c2898667281ad9501cc05a8e31a6f53" + }, + ["pretty.lua"] = "3ece64317ce05916eaba91fa96d9e7c0", + ["seq.lua"] = "e99e420345ab11120a7b741d8184920a", + ["sip.lua"] = "bde74f65e7246017d3ef034d178100ea", + ["strict.lua"] = "720e939931dbbe42fad8fd4e7736435e", + ["stringio.lua"] = "a8f4c786ea1b62f16ed05e6b09840044", + ["stringx.lua"] = "43f57755969c6b4001316226506a3744", + ["tablex.lua"] = "dec027cc3a3901766bd933c5fc0f3e93", + ["template.lua"] = "f358175bbb84c401c6213c953ce295a4", + ["test.lua"] = "1c45f7b1c438673f1eb668e2ca592f1c", + ["text.lua"] = "c30f90cab2d00186a6432e408ba1fe14", + ["utils.lua"] = "68cd38638a29b4ab5f1cc0eae38dce77", + ["xml.lua"] = "e13ed468c450fccb9a8e858a0f787eef" + } + }, + ["penlight-0.9.8-1.rockspec"] = "96edac3ff1d0ac57cb45d6551a56a775" +} diff --git a/Utils/luarocks/luasrcdiet/0.3.0-2/bin/luasrcdiet b/Utils/luarocks/luasrcdiet/0.3.0-2/bin/luasrcdiet new file mode 100644 index 000000000..28e62894a --- /dev/null +++ b/Utils/luarocks/luasrcdiet/0.3.0-2/bin/luasrcdiet @@ -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 specify file name to write output + -s suffix for output files (default '_') + --keep keep block comment with inside + --plugin run 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 diff --git a/Utils/luarocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc b/Utils/luarocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc new file mode 100644 index 000000000..345d581b5 --- /dev/null +++ b/Utils/luarocks/luasrcdiet/0.3.0-2/doc/features-and-usage.adoc @@ -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 <> 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 LuaSrcDiet’s 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. diff --git a/Utils/luarocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc b/Utils/luarocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc new file mode 100644 index 000000000..3ee744e7d --- /dev/null +++ b/Utils/luarocks/luasrcdiet/0.3.0-2/doc/performance-stats.adoc @@ -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 program’s 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=" 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 can’t 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 Lua’s 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 don’t 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. + +A’s 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 B’s activation-to-removal period is isolated from the time of A’s first access to last access, meaning B’s active time will never affect any of A’s 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 A’s 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 A’s accesses. +For no collision, all of A’s 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 isn’t 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 user’s 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._ diff --git a/Utils/luarocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec b/Utils/luarocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec new file mode 100644 index 000000000..eb77bef49 --- /dev/null +++ b/Utils/luarocks/luasrcdiet/0.3.0-2/luasrcdiet-0.3.0-2.rockspec @@ -0,0 +1,41 @@ +-- vim: set ft=lua: + +package = 'LuaSrcDiet' +version = '0.3.0-2' + + source = { url = 'https://github.com/jirutka/luasrcdiet/archive/v0.3.0/luasrcdiet-0.3.0.tar.gz', md5 = 'c0ff36ef66cd0568c96bc54e9253a8fa' } + +description = { + summary = 'Compresses Lua source code by removing unnecessary characters', + detailed = [[ +This is revival of LuaSrcDiet originally written by Kein-Hong Man.]], + homepage = 'https://github.com/jirutka/luasrcdiet', + maintainer = 'Jakub Jirutka ', + license = 'MIT', +} + +dependencies = { + 'lua >= 5.1', +} + +build = { + type = 'builtin', + modules = { + ['luasrcdiet'] = 'luasrcdiet/init.lua', + ['luasrcdiet.equiv'] = 'luasrcdiet/equiv.lua', + ['luasrcdiet.fs'] = 'luasrcdiet/fs.lua', + ['luasrcdiet.llex'] = 'luasrcdiet/llex.lua', + ['luasrcdiet.lparser'] = 'luasrcdiet/lparser.lua', + ['luasrcdiet.optlex'] = 'luasrcdiet/optlex.lua', + ['luasrcdiet.optparser'] = 'luasrcdiet/optparser.lua', + ['luasrcdiet.plugin.example'] = 'luasrcdiet/plugin/example.lua', + ['luasrcdiet.plugin.html'] = 'luasrcdiet/plugin/html.lua', + ['luasrcdiet.plugin.sloc'] = 'luasrcdiet/plugin/sloc.lua', + ['luasrcdiet.utils'] = 'luasrcdiet/utils.lua', + }, + install = { + bin = { + luasrcdiet = 'bin/luasrcdiet', + } + } +} diff --git a/Utils/luarocks/luasrcdiet/0.3.0-2/rock_manifest b/Utils/luarocks/luasrcdiet/0.3.0-2/rock_manifest new file mode 100644 index 000000000..2e7628412 --- /dev/null +++ b/Utils/luarocks/luasrcdiet/0.3.0-2/rock_manifest @@ -0,0 +1,28 @@ +rock_manifest = { + bin = { + luasrcdiet = "6c318685d57f827cf5baf7037a5d6072" + }, + doc = { + ["features-and-usage.adoc"] = "157587c27a0c340d9d1dd06af9b339b5", + ["performance-stats.adoc"] = "cf5f96a86e021a3a584089fafcabd056", + ["tech-notes.adoc"] = "075bc34e667a0055e659e656baa2365a" + }, + lua = { + luasrcdiet = { + ["equiv.lua"] = "967a6b17573d229e326dbb740ad7fe8c", + ["fs.lua"] = "53db7dfc50d026b683fad68ed70ead0f", + ["init.lua"] = "c6f368e6cf311f3257067fed0fbcd06a", + ["llex.lua"] = "ede897af261fc362a82d87fbad91ea2b", + ["lparser.lua"] = "c1e1f04d412b79a040fd1c2b74112953", + ["optlex.lua"] = "7c986da991a338494c36770b4a30fa9f", + ["optparser.lua"] = "b125a271ac1c691dec68b63019b1b5da", + plugin = { + ["example.lua"] = "86b5c1e9dc7959db6b221d6d5a0db3d1", + ["html.lua"] = "c0d3336a133f0c8663f395ee98d54f6a", + ["sloc.lua"] = "fb1a91b18b701ab83f21c87733be470a" + }, + ["utils.lua"] = "bd6c1e85c6a9bf3383d336a4797fb292" + } + }, + ["luasrcdiet-0.3.0-2.rockspec"] = "da70047e1b0cbdc1ff08d060327fa110" +} diff --git a/Utils/luarocks/share/lua/5.1/defaultcss.lua b/Utils/luarocks/share/lua/5.1/defaultcss.lua new file mode 100644 index 000000000..2c2b331cc --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/defaultcss.lua @@ -0,0 +1,270 @@ +return [[html { + color: #000; + background: #FFF; +} +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { + margin: 0; + padding: 0; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +fieldset,img { + border: 0; +} +address,caption,cite,code,dfn,em,strong,th,var,optgroup { + font-style: inherit; + font-weight: inherit; +} +del,ins { + text-decoration: none; +} +li { + list-style: bullet; + margin-left: 20px; +} +caption,th { + text-align: left; +} +h1,h2,h3,h4,h5,h6 { + font-size: 100%; + font-weight: bold; +} +q:before,q:after { + content: ''; +} +abbr,acronym { + border: 0; + font-variant: normal; +} +sup { + vertical-align: baseline; +} +sub { + vertical-align: baseline; +} +legend { + color: #000; +} +input,button,textarea,select,optgroup,option { + font-family: inherit; + font-size: inherit; + font-style: inherit; + font-weight: inherit; +} +input,button,textarea,select {*font-size:100%; +} +/* END RESET */ + +body { + margin-left: 1em; + margin-right: 1em; + font-family: arial, helvetica, geneva, sans-serif; + background-color: #ffffff; margin: 0px; +} + +code, tt { font-family: monospace; } + +body, p, td, th { font-size: .95em; line-height: 1.2em;} + +p, ul { margin: 10px 0 0 10px;} + +strong { font-weight: bold;} + +em { font-style: italic;} + +h1 { + font-size: 1.5em; + margin: 25px 0 20px 0; +} +h2, h3, h4 { margin: 15px 0 10px 0; } +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; + background: #00007f; + height: 1px; +} + +blockquote { margin-left: 3em; } + +ul { list-style-type: disc; } + +p.name { + font-family: "Andale Mono", monospace; + padding-top: 1em; +} + +p:first-child { + margin-top: 0px; +} + +pre.example { + background-color: rgb(245, 245, 245); + border: 1px solid silver; + padding: 10px; + margin: 10px 0 10px 0; + font-family: "Andale Mono", monospace; + font-size: .85em; +} + +pre { + background-color: rgb(245, 245, 245); + border: 1px solid silver; + padding: 10px; + margin: 10px 0 10px 0; + font-family: "Andale Mono", monospace; +} + + +table.index { border: 1px #00007f; } +table.index td { text-align: left; vertical-align: top; } + +#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; +} + +#main { + background-color: #f0f0f0; + border-left: 2px solid #cccccc; +} + +#navigation { + float: left; + width: 18em; + vertical-align: top; + background-color: #f0f0f0; + overflow: scroll; + position: fixed; + height:100%; +} + +#navigation h2 { + background-color:#e7e7e7; + font-size:1.1em; + color:#000000; + text-align: left; + padding:0.2em; + border-top:1px solid #dddddd; + border-bottom:1px solid #dddddd; +} + +#navigation ul +{ + font-size:1em; + list-style-type: none; + margin: 1px 1px 10px 1px; +} + +#navigation li { + text-indent: -1em; + display: block; + margin: 3px 0px 0px 22px; +} + +#navigation li li a { + margin: 0px 3px 0px -1em; +} + +#content { + margin-left: 18em; + padding: 1em; + border-left: 2px solid #cccccc; + border-right: 2px solid #cccccc; + background-color: #ffffff; +} + +#about { + clear: both; + padding: 5px; + border-top: 2px solid #cccccc; + background-color: #ffffff; +} + +@media print { + body { + font: 12pt "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 { + padding: 1em; + background-color: #ffffff; + } + + #navigation { + display: none; + } + pre.example { + font-family: "Andale Mono", monospace; + font-size: 10pt; + page-break-inside: avoid; + } +} + +table.module_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.module_list td { + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.module_list td.name { background-color: #f0f0f0; } +table.module_list td.summary { width: 100%; } + + +table.function_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.function_list td { + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.function_list td.name { background-color: #f0f0f0; } +table.function_list td.summary { width: 100%; } + +dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} +dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} +dl.table h3, dl.function h3 {font-size: .95em;} + +]] diff --git a/Utils/luarocks/share/lua/5.1/docgenerator.lua b/Utils/luarocks/share/lua/5.1/docgenerator.lua new file mode 100644 index 000000000..2a4d88812 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/docgenerator.lua @@ -0,0 +1,87 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- + +-- +-- Load documentation generator and update its path +-- +local templateengine = require 'templateengine' +for name, def in pairs( require 'template.utils' ) do + templateengine.env [ name ] = def +end + +-- Load documentation extractor and set handled languages +local lddextractor = require 'lddextractor' + +local M = {} +M.defaultsitemainpagename = 'index' + +function M.generatedocforfiles(filenames, cssname,noheuristic) + if not filenames then return nil, 'No files provided.' end + -- + -- Generate API model elements for all files + -- + local generatedfiles = {} + local wrongfiles = {} + for _, filename in pairs( filenames ) do + -- Load file content + local file, error = io.open(filename, 'r') + if not file then return nil, 'Unable to read "'..filename..'"\n'..err end + local code = file:read('*all') + file:close() + -- Get module for current file + local apimodule, err = lddextractor.generateapimodule(filename, code,noheuristic) + + -- Handle modules with module name + if apimodule and apimodule.name then + generatedfiles[ apimodule.name ] = apimodule + elseif not apimodule then + -- Track faulty files + table.insert(wrongfiles, 'Unable to extract comments from "'..filename..'".\n'..err) + elseif not apimodule.name then + -- Do not generate documentation for unnamed modules + table.insert(wrongfiles, 'Unable to create documentation for "'..filename..'", no module name provided.') + end + end + -- + -- Defining index, which will summarize all modules + -- + local index = { + modules = generatedfiles, + name = M.defaultsitemainpagename, + tag='index' + } + generatedfiles[ M.defaultsitemainpagename ] = index + + -- + -- Define page cursor + -- + local page = { + currentmodule = nil, + headers = { [[]] }, + modules = generatedfiles, + tag = 'page' + } + + -- + -- Iterate over modules, generating complete doc pages + -- + for _, module in pairs( generatedfiles ) do + -- Update current cursor page + page.currentmodule = module + -- Generate page + local content, error = templateengine.applytemplate(page) + if not content then return nil, error end + module.body = content + end + return generatedfiles, wrongfiles +end +return M diff --git a/Utils/luarocks/share/lua/5.1/extractors.lua b/Utils/luarocks/share/lua/5.1/extractors.lua new file mode 100644 index 000000000..aa5235ea9 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/extractors.lua @@ -0,0 +1,102 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local M = {} +require 'metalua.loader' +local compiler = require 'metalua.compiler' +local mlc = compiler.new() +local Q = require 'metalua.treequery' + +-- Enable to retrieve all Javadoc-like comments from C code +function M.c(code) + if not code then return nil, 'No code provided' end + local comments = {} + -- Loop over comments stripping cosmetic '*' + for comment in code:gmatch('%s*/%*%*+(.-)%*+/') do + -- All Lua special comment are prefixed with an '-', + -- so we also comment C comment to make them compliant + table.insert(comments, '-'..comment) + end + return comments +end + +-- Enable to retrieve "---" comments from Lua code +function M.lua( code ) + if not code then return nil, 'No code provided' end + + -- manage shebang + if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end + + -- check for errors + local f, err = loadstring(code,'source_to_check') + if not f then + return nil, 'Syntax error.\n' .. err + end + + -- Get ast from file + local status, ast = pcall(mlc.src_to_ast, mlc, code) + -- + -- Detect parsing errors + -- + if not status then + return nil, 'There might be a syntax error.\n' .. ast + end + + -- + -- Extract commented nodes from AST + -- + + -- Function enabling commented node selection + local function acceptcommentednode(node) + return node.lineinfo and ( node.lineinfo.last.comments or node.lineinfo.first.comments ) + end + + -- Fetch commented node from AST + local commentednodes = Q(ast):filter( acceptcommentednode ):list() + + -- Comment cache to avoid selecting same comment twice + local commentcache = {} + -- Will contain selected comments + local comments = {} + + -- Loop over commented nodes + for _, node in ipairs( commentednodes ) do + + -- A node can is relateds to comment before and after itself, + -- the following gathers them. + local commentlists = {} + if node.lineinfo and node.lineinfo.first.comments then + table.insert(commentlists, node.lineinfo.first.comments) + end + if node.lineinfo and node.lineinfo.last.comments then + table.insert(commentlists, node.lineinfo.last.comments) + end + -- Now that we have comments before and fater the node, + -- collect them in a single table + for _, list in ipairs( commentlists ) do + for _, commenttable in ipairs(list) do + -- Only select special comments + local firstcomment = #commenttable > 0 and #commenttable[1] > 0 and commenttable[1] + if firstcomment:sub(1, 1) == '-' then + for _, comment in ipairs( commenttable ) do + -- Only comments which were not already collected + if not commentcache[comment] then + commentcache[comment] = true + table.insert(comments, comment) + end + end + end + end + end + end + return comments +end +return M diff --git a/Utils/luarocks/share/lua/5.1/fs/lfs.lua b/Utils/luarocks/share/lua/5.1/fs/lfs.lua new file mode 100644 index 000000000..3fc513542 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/fs/lfs.lua @@ -0,0 +1,130 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local lfs = require 'lfs' +local M = {} +local function iswindows() + local p = io.popen("echo %os%") + if not p then + return false + end + local result =p:read("*l") + p:close() + return result == "Windows_NT" +end +M.separator = iswindows() and [[\]] or [[/]] +--- +-- Will recursively browse given directories and list files encountered +-- @param tab Table, list where files will be added +-- @param dirorfiles list of path to browse in order to build list. +-- Files from this list will be added to tab list. +-- @return tab list, table containing all files from directories +-- and files contained in dirorfile +local function appendfiles(tab, dirorfile) + + -- Nothing to process + if #dirorfile < 1 then return tab end + + -- Append all files to list + local dirs = {} + for _, path in ipairs( dirorfile ) do + -- Determine element nature + local elementnature = lfs.attributes (path, "mode") + + -- Handle files + if elementnature == 'file' then + table.insert(tab, path) + else if elementnature == 'directory' then + + -- Check if folder is accessible + local status, error = pcall(lfs.dir, path) + if not status then return nil, error end + + -- + -- Handle folders + -- + for diskelement in lfs.dir(path) do + + -- Format current file name + local currentfilename + if path:sub(#path) == M.separator then + currentfilename = path .. diskelement + else + currentfilename = path .. M.separator .. diskelement + end + + -- Handle folder elements + local nature, err = lfs.attributes (currentfilename, "mode") + -- Append file to current list + if nature == 'file' then + table.insert(tab, currentfilename) + elseif nature == 'directory' then + -- Avoid current and parent directory in order to avoid + -- endless recursion + if diskelement ~= '.' and diskelement ~= '..' then + -- Handle subfolders + table.insert(dirs, currentfilename) + end + end + end + end + end + end + -- If we only encountered files, going deeper is useless + if #dirs == 0 then return tab end + -- Append files from encountered directories + return appendfiles(tab, dirs) +end +--- +-- Provide a list of files from a directory +-- @param list Table of directories to browse +-- @return table of string, path to files contained in given directories +function M.filelist(list) + if not list then return nil, 'No directory list provided' end + return appendfiles({}, list) +end +function M.checkdirectory( dirlist ) + if not dirlist then return false end + local missingdirs = {} + for _, filename in ipairs( dirlist ) do + if not lfs.attributes(filename, 'mode') then + table.insert(missingdirs, filename) + end + end + if #missingdirs > 0 then + return false, missingdirs + end + return true +end +function M.fill(filename, content) + -- + -- Ensure parent directory exists + -- + local parent = filename:gmatch([[(.*)]] .. M.separator ..[[(.+)]])() + local parentnature = lfs.attributes(parent, 'mode') + -- Create parent directory while absent + if not parentnature then + lfs.mkdir( parent ) + elseif parentnature ~= 'directory' then + -- Notify that disk element already exists + return nil, parent..' is a '..parentnature..'.' + end + + -- Create actual file + local file, error = io.open(filename, 'w') + if not file then + return nil, error + end + file:write( content ) + file:close() + return true +end +return M diff --git a/Utils/luarocks/share/lua/5.1/lddextractor.lua b/Utils/luarocks/share/lua/5.1/lddextractor.lua new file mode 100644 index 000000000..b2cea2ea3 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/lddextractor.lua @@ -0,0 +1,113 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +require 'metalua.loader' +local compiler = require 'metalua.compiler' +local mlc = compiler.new() +local M = {} + +-- +-- Define default supported languages +-- +M.supportedlanguages = {} +local extractors = require 'extractors' + +-- Support Lua comment extracting +M.supportedlanguages['lua'] = extractors.lua + +-- Support C comment extracting +for _,c in ipairs({'c', 'cpp', 'c++'}) do + M.supportedlanguages[c] = extractors.c +end + +-- Extract comment from code, +-- type of code is deduced from filename extension +function M.extract(filename, code) + -- Check parameters + if not code then return nil, 'No code provided' end + if type(filename) ~= "string" then + return nil, 'No string for file name provided' + end + + -- Extract file extension + local fileextension = filename:gmatch('.*%.(.*)')() + if not fileextension then + return nil, 'File '..filename..' has no extension, could not determine how to extract documentation.' + end + + -- Check if it is possible to extract documentation from these files + local extractor = M.supportedlanguages[ fileextension ] + if not extractor then + return nil, 'Unable to extract documentation from '.. fileextension .. ' file.' + end + return extractor( code ) +end +-- Generate a file gathering only comments from given code +function M.generatecommentfile(filename, code) + local comments, error = M.extract(filename, code) + if not comments then + return nil, 'Unable to generate comment file.\n'..error + end + local filecontent = {} + for _, comment in ipairs( comments ) do + table.insert(filecontent, "--[[") + table.insert(filecontent, comment) + table.insert(filecontent, "\n]]\n\n") + end + return table.concat(filecontent)..'return nil\n' +end +-- Create API Model module from a 'comment only' lua file +function M.generateapimodule(filename, code,noheuristic) + if not filename then return nil, 'No file name given.' end + if not code then return nil, 'No code provided.' end + if type(filename) ~= "string" then return nil, 'No string for file name provided' end + + -- for non lua file get comment file + if filename:gmatch('.*%.(.*)')() ~= 'lua' then + local err + code, err = M.generatecommentfile(filename, code) + if not code then + return nil, 'Unable to create api module for "'..filename..'".\n'..err + end + else + + -- manage shebang + if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end + + -- check for errors + local f, err = loadstring(code,'source_to_check') + if not f then + return nil, 'File'..filename..'contains syntax error.\n' .. err + end + end + + local status, ast = pcall(mlc.src_to_ast, mlc, code) + if not status then + return nil, 'Unable to compute ast for "'..filename..'".\n'..ast + end + + -- Extract module name as the filename without extension + local modulename + local matcher = string.gmatch(filename,'.*/(.*)%..*$') + if matcher then modulename = matcher() end + + -- Create api model + local apimodelbuilder = require 'models.apimodelbuilder' + local _file, comment2apiobj = apimodelbuilder.createmoduleapi(ast, modulename) + + -- Create internal model + if not noheuristic then + local internalmodelbuilder = require "models.internalmodelbuilder" + local _internalcontent = internalmodelbuilder.createinternalcontent(ast,_file,comment2apiobj, modulename) + end + return _file +end +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/equiv.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/equiv.lua new file mode 100644 index 000000000..3efa4efe7 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/equiv.lua @@ -0,0 +1,465 @@ +--------- +-- Source and binary equivalency comparisons +-- +-- **Notes:** +-- +-- * Intended as an extra safety check for mission-critical code, +-- should give affirmative results if everything works. +-- * Heavy on load() and string.dump(), which may be slowish, +-- and may cause problems for cross-compiled applications. +-- * Optional detailed information dump is mainly for debugging, +-- reason being, if the two are not equivalent when they should be, +-- then some form of optimization has failed. +-- * source: IMPORTANT: TK_NAME not compared if opt-locals enabled. +-- * binary: IMPORTANT: Some shortcuts are taken with int and size_t +-- value reading -- if the functions break, then the binary chunk +-- is very large indeed. +-- * binary: There is a lack of diagnostic information when a compare +-- fails; you can use ChunkSpy and compare using visual diff. +---- +local byte = string.byte +local dump = string.dump +local load = loadstring or load --luacheck: ignore 113 +local sub = string.sub + +local M = {} + +local is_realtoken = { -- significant (grammar) tokens + TK_KEYWORD = true, + TK_NAME = true, + TK_NUMBER = true, + TK_STRING = true, + TK_LSTRING = true, + TK_OP = true, + TK_EOS = true, +} + +local option, llex, warn + + +--- The initialization function. +-- +-- @tparam {[string]=bool,...} _option +-- @tparam luasrcdiet.llex _llex +-- @tparam table _warn +function M.init(_option, _llex, _warn) + option = _option + llex = _llex + warn = _warn +end + +--- Builds lists containing a 'normal' lexer stream. +-- +-- @tparam string s The source code. +-- @treturn table +-- @treturn table +local function build_stream(s) + local stok, sseminfo = llex.lex(s) -- source list (with whitespace elements) + local tok, seminfo -- processed list (real elements only) + = {}, {} + for i = 1, #stok do + local t = stok[i] + if is_realtoken[t] then + tok[#tok + 1] = t + seminfo[#seminfo + 1] = sseminfo[i] + end + end--for + return tok, seminfo +end + +-- Tests source (lexer stream) equivalence. +-- +-- @tparam string z +-- @tparam string dat +function M.source(z, dat) + + -- Returns a dumped string for seminfo compares. + local function dumpsem(s) + local sf = load("return "..s, "z") + if sf then + return dump(sf) + end + end + + -- Marks and optionally reports non-equivalence. + local function bork(msg) + if option.DETAILS then print("SRCEQUIV: "..msg) end + warn.SRC_EQUIV = true + end + + -- Get lexer streams for both source strings, compare. + local tok1, seminfo1 = build_stream(z) -- original + local tok2, seminfo2 = build_stream(dat) -- compressed + + -- Compare shbang lines ignoring EOL. + local sh1 = z:match("^(#[^\r\n]*)") + local sh2 = dat:match("^(#[^\r\n]*)") + if sh1 or sh2 then + if not sh1 or not sh2 or sh1 ~= sh2 then + bork("shbang lines different") + end + end + + -- Compare by simple count. + if #tok1 ~= #tok2 then + bork("count "..#tok1.." "..#tok2) + return + end + + -- Compare each element the best we can. + for i = 1, #tok1 do + local t1, t2 = tok1[i], tok2[i] + local s1, s2 = seminfo1[i], seminfo2[i] + if t1 ~= t2 then -- by type + bork("type ["..i.."] "..t1.." "..t2) + break + end + if t1 == "TK_KEYWORD" or t1 == "TK_NAME" or t1 == "TK_OP" then + if t1 == "TK_NAME" and option["opt-locals"] then + -- can't compare identifiers of locals that are optimized + elseif s1 ~= s2 then -- by semantic info (simple) + bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2) + break + end + elseif t1 == "TK_EOS" then + -- no seminfo to compare + else-- "TK_NUMBER" or "TK_STRING" or "TK_LSTRING" + -- compare 'binary' form, so dump a function + local s1b,s2b = dumpsem(s1), dumpsem(s2) + if not s1b or not s2b or s1b ~= s2b then + bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2) + break + end + end + end--for + + -- Successful comparison if end is reached with no borks. +end + +--- Tests binary chunk equivalence (only for PUC Lua 5.1). +-- +-- @tparam string z +-- @tparam string dat +function M.binary(z, dat) + local TNIL = 0 --luacheck: ignore 211 + local TBOOLEAN = 1 + local TNUMBER = 3 + local TSTRING = 4 + + -- sizes of data types + local endian + local sz_int + local sz_sizet + local sz_inst + local sz_number + local getint + local getsizet + + -- Marks and optionally reports non-equivalence. + local function bork(msg) + if option.DETAILS then print("BINEQUIV: "..msg) end + warn.BIN_EQUIV = true + end + + -- Checks if bytes exist. + local function ensure(c, sz) + if c.i + sz - 1 > c.len then return end + return true + end + + -- Skips some bytes. + local function skip(c, sz) + if not sz then sz = 1 end + c.i = c.i + sz + end + + -- Returns a byte value. + local function getbyte(c) + local i = c.i + if i > c.len then return end + local d = sub(c.dat, i, i) + c.i = i + 1 + return byte(d) + end + + -- Return an int value (little-endian). + local function getint_l(c) + local n, scale = 0, 1 + if not ensure(c, sz_int) then return end + for _ = 1, sz_int do + n = n + scale * getbyte(c) + scale = scale * 256 + end + return n + end + + -- Returns an int value (big-endian). + local function getint_b(c) + local n = 0 + if not ensure(c, sz_int) then return end + for _ = 1, sz_int do + n = n * 256 + getbyte(c) + end + return n + end + + -- Returns a size_t value (little-endian). + local function getsizet_l(c) + local n, scale = 0, 1 + if not ensure(c, sz_sizet) then return end + for _ = 1, sz_sizet do + n = n + scale * getbyte(c) + scale = scale * 256 + end + return n + end + + -- Returns a size_t value (big-endian). + local function getsizet_b(c) + local n = 0 + if not ensure(c, sz_sizet) then return end + for _ = 1, sz_sizet do + n = n * 256 + getbyte(c) + end + return n + end + + -- Returns a block (as a string). + local function getblock(c, sz) + local i = c.i + local j = i + sz - 1 + if j > c.len then return end + local d = sub(c.dat, i, j) + c.i = i + sz + return d + end + + -- Returns a string. + local function getstring(c) + local n = getsizet(c) + if not n then return end + if n == 0 then return "" end + return getblock(c, n) + end + + -- Compares byte value. + local function goodbyte(c1, c2) + local b1, b2 = getbyte(c1), getbyte(c2) + if not b1 or not b2 or b1 ~= b2 then + return + end + return b1 + end + + -- Compares byte value. + local function badbyte(c1, c2) + local b = goodbyte(c1, c2) + if not b then return true end + end + + -- Compares int value. + local function goodint(c1, c2) + local i1, i2 = getint(c1), getint(c2) + if not i1 or not i2 or i1 ~= i2 then + return + end + return i1 + end + + -- Recursively-called function to compare function prototypes. + local function getfunc(c1, c2) + -- source name (ignored) + if not getstring(c1) or not getstring(c2) then + bork("bad source name"); return + end + -- linedefined (ignored) + if not getint(c1) or not getint(c2) then + bork("bad linedefined"); return + end + -- lastlinedefined (ignored) + if not getint(c1) or not getint(c2) then + bork("bad lastlinedefined"); return + end + if not (ensure(c1, 4) and ensure(c2, 4)) then + bork("prototype header broken") + end + -- nups (compared) + if badbyte(c1, c2) then + bork("bad nups"); return + end + -- numparams (compared) + if badbyte(c1, c2) then + bork("bad numparams"); return + end + -- is_vararg (compared) + if badbyte(c1, c2) then + bork("bad is_vararg"); return + end + -- maxstacksize (compared) + if badbyte(c1, c2) then + bork("bad maxstacksize"); return + end + -- code (compared) + local ncode = goodint(c1, c2) + if not ncode then + bork("bad ncode"); return + end + local code1 = getblock(c1, ncode * sz_inst) + local code2 = getblock(c2, ncode * sz_inst) + if not code1 or not code2 or code1 ~= code2 then + bork("bad code block"); return + end + -- constants (compared) + local nconst = goodint(c1, c2) + if not nconst then + bork("bad nconst"); return + end + for _ = 1, nconst do + local ctype = goodbyte(c1, c2) + if not ctype then + bork("bad const type"); return + end + if ctype == TBOOLEAN then + if badbyte(c1, c2) then + bork("bad boolean value"); return + end + elseif ctype == TNUMBER then + local num1 = getblock(c1, sz_number) + local num2 = getblock(c2, sz_number) + if not num1 or not num2 or num1 ~= num2 then + bork("bad number value"); return + end + elseif ctype == TSTRING then + local str1 = getstring(c1) + local str2 = getstring(c2) + if not str1 or not str2 or str1 ~= str2 then + bork("bad string value"); return + end + end + end + -- prototypes (compared recursively) + local nproto = goodint(c1, c2) + if not nproto then + bork("bad nproto"); return + end + for _ = 1, nproto do + if not getfunc(c1, c2) then + bork("bad function prototype"); return + end + end + -- debug information (ignored) + -- lineinfo (ignored) + local sizelineinfo1 = getint(c1) + if not sizelineinfo1 then + bork("bad sizelineinfo1"); return + end + local sizelineinfo2 = getint(c2) + if not sizelineinfo2 then + bork("bad sizelineinfo2"); return + end + if not getblock(c1, sizelineinfo1 * sz_int) then + bork("bad lineinfo1"); return + end + if not getblock(c2, sizelineinfo2 * sz_int) then + bork("bad lineinfo2"); return + end + -- locvars (ignored) + local sizelocvars1 = getint(c1) + if not sizelocvars1 then + bork("bad sizelocvars1"); return + end + local sizelocvars2 = getint(c2) + if not sizelocvars2 then + bork("bad sizelocvars2"); return + end + for _ = 1, sizelocvars1 do + if not getstring(c1) or not getint(c1) or not getint(c1) then + bork("bad locvars1"); return + end + end + for _ = 1, sizelocvars2 do + if not getstring(c2) or not getint(c2) or not getint(c2) then + bork("bad locvars2"); return + end + end + -- upvalues (ignored) + local sizeupvalues1 = getint(c1) + if not sizeupvalues1 then + bork("bad sizeupvalues1"); return + end + local sizeupvalues2 = getint(c2) + if not sizeupvalues2 then + bork("bad sizeupvalues2"); return + end + for _ = 1, sizeupvalues1 do + if not getstring(c1) then bork("bad upvalues1"); return end + end + for _ = 1, sizeupvalues2 do + if not getstring(c2) then bork("bad upvalues2"); return end + end + return true + end + + -- Removes shbang line so that load runs. + local function zap_shbang(s) + local shbang = s:match("^(#[^\r\n]*\r?\n?)") + if shbang then -- cut out shbang + s = sub(s, #shbang + 1) + end + return s + end + + -- Attempt to compile, then dump to get binary chunk string. + local cz = load(zap_shbang(z), "z") + if not cz then + bork("failed to compile original sources for binary chunk comparison") + return + end + + local cdat = load(zap_shbang(dat), "z") + if not cdat then + bork("failed to compile compressed result for binary chunk comparison") + end + + -- if load() works, dump assuming string.dump() is error-free + local c1 = { i = 1, dat = dump(cz) } + c1.len = #c1.dat + + local c2 = { i = 1, dat = dump(cdat) } + c2.len = #c2.dat + + -- Parse binary chunks to verify equivalence. + -- * For headers, handle sizes to allow a degree of flexibility. + -- * Assume a valid binary chunk is generated, since it was not + -- generated via external means. + if not (ensure(c1, 12) and ensure(c2, 12)) then + bork("header broken") + end + skip(c1, 6) -- skip signature(4), version, format + endian = getbyte(c1) -- 1 = little endian + sz_int = getbyte(c1) -- get data type sizes + sz_sizet = getbyte(c1) + sz_inst = getbyte(c1) + sz_number = getbyte(c1) + skip(c1) -- skip integral flag + skip(c2, 12) -- skip other header (assume similar) + + if endian == 1 then -- set for endian sensitive data we need + getint = getint_l + getsizet = getsizet_l + else + getint = getint_b + getsizet = getsizet_b + end + getfunc(c1, c2) -- get prototype at root + + if c1.i ~= c1.len + 1 then + bork("inconsistent binary chunk1"); return + elseif c2.i ~= c2.len + 1 then + bork("inconsistent binary chunk2"); return + end + + -- Successful comparison if end is reached with no borks. +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/fs.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/fs.lua new file mode 100644 index 000000000..00baa113c --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/fs.lua @@ -0,0 +1,74 @@ +--------- +-- Utility functions for operations on a file system. +-- +-- **Note: This module is not part of public API!** +---- +local fmt = string.format +local open = io.open + +local UTF8_BOM = '\239\187\191' + +local function normalize_io_error (name, err) + if err:sub(1, #name + 2) == name..': ' then + err = err:sub(#name + 3) + end + return err +end + +local M = {} + +--- Reads the specified file and returns its content as string. +-- +-- @tparam string filename Path of the file to read. +-- @tparam string mode The mode in which to open the file, see @{io.open} (default: "r"). +-- @treturn[1] string A content of the file. +-- @treturn[2] nil +-- @treturn[2] string An error message. +function M.read_file (filename, mode) + local handler, err = open(filename, mode or 'r') + if not handler then + return nil, fmt('Could not open %s for reading: %s', + filename, normalize_io_error(filename, err)) + end + + local content, err = handler:read('*a') --luacheck: ignore 411 + if not content then + return nil, fmt('Could not read %s: %s', filename, normalize_io_error(filename, err)) + end + + handler:close() + + if content:sub(1, #UTF8_BOM) == UTF8_BOM then + content = content:sub(#UTF8_BOM + 1) + end + + return content +end + +--- Writes the given data to the specified file. +-- +-- @tparam string filename Path of the file to write. +-- @tparam string data The data to write. +-- @tparam ?string mode The mode in which to open the file, see @{io.open} (default: "w"). +-- @treturn[1] true +-- @treturn[2] nil +-- @treturn[2] string An error message. +function M.write_file (filename, data, mode) + local handler, err = open(filename, mode or 'w') + if not handler then + return nil, fmt('Could not open %s for writing: %s', + filename, normalize_io_error(filename, err)) + end + + local _, err = handler:write(data) --luacheck: ignore 411 + if err then + return nil, fmt('Could not write %s: %s', filename, normalize_io_error(filename, err)) + end + + handler:flush() + handler:close() + + return true +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/init.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/init.lua new file mode 100644 index 000000000..8b47eed72 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/init.lua @@ -0,0 +1,117 @@ +--------- +-- LuaSrcDiet API +---- +local equiv = require 'luasrcdiet.equiv' +local llex = require 'luasrcdiet.llex' +local lparser = require 'luasrcdiet.lparser' +local optlex = require 'luasrcdiet.optlex' +local optparser = require 'luasrcdiet.optparser' +local utils = require 'luasrcdiet.utils' + +local concat = table.concat +local merge = utils.merge + +local _ -- placeholder + + +local function noop () + return +end + +local function opts_to_legacy (opts) + local res = {} + for key, val in pairs(opts) do + res['opt-'..key] = val + end + return res +end + + +local M = {} + +--- The module's name. +M._NAME = 'luasrcdiet' + +--- The module's version number. +M._VERSION = '0.3.0' + +--- The module's homepage. +M._HOMEPAGE = 'https://github.com/jirutka/luasrcdiet' + +--- All optimizations disabled. +M.NONE_OPTS = { + binequiv = false, + comments = false, + emptylines = false, + entropy = false, + eols = false, + experimental = false, + locals = false, + numbers = false, + srcequiv = false, + strings = false, + whitespace = false, +} + +--- Basic optimizations enabled. +-- @table BASIC_OPTS +M.BASIC_OPTS = merge(M.NONE_OPTS, { + comments = true, + emptylines = true, + srcequiv = true, + whitespace = true, +}) + +--- Defaults. +-- @table DEFAULT_OPTS +M.DEFAULT_OPTS = merge(M.BASIC_OPTS, { + locals = true, + numbers = true, +}) + +--- Maximum optimizations enabled (all except experimental). +-- @table MAXIMUM_OPTS +M.MAXIMUM_OPTS = merge(M.DEFAULT_OPTS, { + entropy = true, + eols = true, + strings = true, +}) + +--- Optimizes the given Lua source code. +-- +-- @tparam ?{[string]=bool,...} opts Optimizations to do (default is @{DEFAULT_OPTS}). +-- @tparam string source The Lua source code to optimize. +-- @treturn string Optimized source. +-- @raise if the source is malformed, source equivalence test failed, or some +-- other error occured. +function M.optimize (opts, source) + assert(source and type(source) == 'string', + 'bad argument #2: expected string, got a '..type(source)) + + opts = opts and merge(M.NONE_OPTS, opts) or M.DEFAULT_OPTS + local legacy_opts = opts_to_legacy(opts) + + local toklist, seminfolist, toklnlist = llex.lex(source) + local xinfo = lparser.parse(toklist, seminfolist, toklnlist) + + optparser.print = noop + optparser.optimize(legacy_opts, toklist, seminfolist, xinfo) + + local warn = optlex.warn -- use this as a general warning lookup + optlex.print = noop + _, seminfolist = optlex.optimize(legacy_opts, toklist, seminfolist, toklnlist) + local optim_source = concat(seminfolist) + + if opts.srcequiv and not opts.experimental then + equiv.init(legacy_opts, llex, warn) + equiv.source(source, optim_source) + + if warn.SRC_EQUIV then + error('Source equivalence test failed!') + end + end + + return optim_source +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/llex.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/llex.lua new file mode 100644 index 000000000..c9d5a0e8c --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/llex.lua @@ -0,0 +1,350 @@ +--------- +-- Lua 5.1+ lexical analyzer written in Lua. +-- +-- This file is part of LuaSrcDiet, based on Yueliang material. +-- +-- **Notes:** +-- +-- * This is a version of the native 5.1.x lexer from Yueliang 0.4.0, +-- with significant modifications to handle LuaSrcDiet's needs: +-- (1) llex.error is an optional error function handler, +-- (2) seminfo for strings include their delimiters and no +-- translation operations are performed on them. +-- * ADDED shbang handling has been added to support executable scripts. +-- * NO localized decimal point replacement magic. +-- * NO limit to number of lines. +-- * NO support for compatible long strings (LUA\_COMPAT_LSTR). +-- * Added goto keyword and double-colon operator (Lua 5.2+). +---- +local find = string.find +local fmt = string.format +local match = string.match +local sub = string.sub +local tonumber = tonumber + +local M = {} + +local kw = {} +for v in ([[ +and break do else elseif end false for function goto if in +local nil not or repeat return then true until while]]):gmatch("%S+") do + kw[v] = true +end + +local z, -- source stream + sourceid, -- name of source + I, -- position of lexer + buff, -- buffer for strings + ln, -- line number + tok, -- lexed token list + seminfo, -- lexed semantic information list + tokln -- line numbers for messages + + +--- Adds information to token listing. +-- +-- @tparam string token +-- @tparam string info +local function addtoken(token, info) + local i = #tok + 1 + tok[i] = token + seminfo[i] = info + tokln[i] = ln +end + +--- Handles line number incrementation and end-of-line characters. +-- +-- @tparam int i Position of lexer in the source stream. +-- @tparam bool is_tok +-- @treturn int +local function inclinenumber(i, is_tok) + local old = sub(z, i, i) + i = i + 1 -- skip '\n' or '\r' + local c = sub(z, i, i) + if (c == "\n" or c == "\r") and (c ~= old) then + i = i + 1 -- skip '\n\r' or '\r\n' + old = old..c + end + if is_tok then addtoken("TK_EOL", old) end + ln = ln + 1 + I = i + return i +end + +--- Returns a chunk name or id, no truncation for long names. +-- +-- @treturn string +local function chunkid() + if sourceid and match(sourceid, "^[=@]") then + return sub(sourceid, 2) -- remove first char + end + return "[string]" +end + +--- Formats error message and throws error. +-- +-- A simplified version, does not report what token was responsible. +-- +-- @tparam string s +-- @tparam int line The line number. +-- @raise +local function errorline(s, line) + local e = M.error or error + e(fmt("%s:%d: %s", chunkid(), line or ln, s)) +end + +--- Counts separators (`="` in a long string delimiter. +-- +-- @tparam int i Position of lexer in the source stream. +-- @treturn int +local function skip_sep(i) + local s = sub(z, i, i) + i = i + 1 + local count = #match(z, "=*", i) + i = i + count + I = i + return (sub(z, i, i) == s) and count or (-count) - 1 +end + +--- Reads a long string or long comment. +-- +-- @tparam bool is_str +-- @tparam string sep +-- @treturn string +-- @raise if unfinished long string or comment. +local function read_long_string(is_str, sep) + local i = I + 1 -- skip 2nd '[' + local c = sub(z, i, i) + if c == "\r" or c == "\n" then -- string starts with a newline? + i = inclinenumber(i) -- skip it + end + while true do + local p, _, r = find(z, "([\r\n%]])", i) -- (long range match) + if not p then + errorline(is_str and "unfinished long string" or + "unfinished long comment") + end + i = p + if r == "]" then -- delimiter test + if skip_sep(i) == sep then + buff = sub(z, buff, I) + I = I + 1 -- skip 2nd ']' + return buff + end + i = I + else -- newline + buff = buff.."\n" + i = inclinenumber(i) + end + end--while +end + +--- Reads a string. +-- +-- @tparam string del The delimiter. +-- @treturn string +-- @raise if unfinished string or too large escape sequence. +local function read_string(del) + local i = I + while true do + local p, _, r = find(z, "([\n\r\\\"\'])", i) -- (long range match) + if p then + if r == "\n" or r == "\r" then + errorline("unfinished string") + end + i = p + if r == "\\" then -- handle escapes + i = i + 1 + r = sub(z, i, i) + if r == "" then break end -- (EOZ error) + p = find("abfnrtv\n\r", r, 1, true) + + if p then -- special escapes + if p > 7 then + i = inclinenumber(i) + else + i = i + 1 + end + + elseif find(r, "%D") then -- other non-digits + i = i + 1 + + else -- \xxx sequence + local _, q, s = find(z, "^(%d%d?%d?)", i) + i = q + 1 + if s + 1 > 256 then -- UCHAR_MAX + errorline("escape sequence too large") + end + + end--if p + else + i = i + 1 + if r == del then -- ending delimiter + I = i + return sub(z, buff, i - 1) -- return string + end + end--if r + else + break -- (error) + end--if p + end--while + errorline("unfinished string") +end + + +--- Initializes lexer for given source _z and source name _sourceid. +-- +-- @tparam string _z The source code. +-- @tparam string _sourceid Name of the source. +local function init(_z, _sourceid) + z = _z -- source + sourceid = _sourceid -- name of source + I = 1 -- lexer's position in source + ln = 1 -- line number + tok = {} -- lexed token list* + seminfo = {} -- lexed semantic information list* + tokln = {} -- line numbers for messages* + + -- Initial processing (shbang handling). + local p, _, q, r = find(z, "^(#[^\r\n]*)(\r?\n?)") + if p then -- skip first line + I = I + #q + addtoken("TK_COMMENT", q) + if #r > 0 then inclinenumber(I, true) end + end +end + +--- Runs lexer on the given source code. +-- +-- @tparam string source The Lua source to scan. +-- @tparam ?string source_name Name of the source (optional). +-- @treturn {string,...} A list of lexed tokens. +-- @treturn {string,...} A list of semantic information (lexed strings). +-- @treturn {int,...} A list of line numbers. +function M.lex(source, source_name) + init(source, source_name) + + while true do--outer + local i = I + -- inner loop allows break to be used to nicely section tests + while true do --luacheck: ignore 512 + + local p, _, r = find(z, "^([_%a][_%w]*)", i) + if p then + I = i + #r + if kw[r] then + addtoken("TK_KEYWORD", r) -- reserved word (keyword) + else + addtoken("TK_NAME", r) -- identifier + end + break -- (continue) + end + + local p, _, r = find(z, "^(%.?)%d", i) + if p then -- numeral + if r == "." then i = i + 1 end + local _, q, r = find(z, "^%d*[%.%d]*([eE]?)", i) --luacheck: ignore 421 + i = q + 1 + if #r == 1 then -- optional exponent + if match(z, "^[%+%-]", i) then -- optional sign + i = i + 1 + end + end + local _, q = find(z, "^[_%w]*", i) + I = q + 1 + local v = sub(z, p, q) -- string equivalent + if not tonumber(v) then -- handles hex test also + errorline("malformed number") + end + addtoken("TK_NUMBER", v) + break -- (continue) + end + + local p, q, r, t = find(z, "^((%s)[ \t\v\f]*)", i) + if p then + if t == "\n" or t == "\r" then -- newline + inclinenumber(i, true) + else + I = q + 1 -- whitespace + addtoken("TK_SPACE", r) + end + break -- (continue) + end + + local _, q = find(z, "^::", i) + if q then + I = q + 1 + addtoken("TK_OP", "::") + break -- (continue) + end + + local r = match(z, "^%p", i) + if r then + buff = i + local p = find("-[\"\'.=<>~", r, 1, true) --luacheck: ignore 421 + if p then + + -- two-level if block for punctuation/symbols + if p <= 2 then + if p == 1 then -- minus + local c = match(z, "^%-%-(%[?)", i) + if c then + i = i + 2 + local sep = -1 + if c == "[" then + sep = skip_sep(i) + end + if sep >= 0 then -- long comment + addtoken("TK_LCOMMENT", read_long_string(false, sep)) + else -- short comment + I = find(z, "[\n\r]", i) or (#z + 1) + addtoken("TK_COMMENT", sub(z, buff, I - 1)) + end + break -- (continue) + end + -- (fall through for "-") + else -- [ or long string + local sep = skip_sep(i) + if sep >= 0 then + addtoken("TK_LSTRING", read_long_string(true, sep)) + elseif sep == -1 then + addtoken("TK_OP", "[") + else + errorline("invalid long string delimiter") + end + break -- (continue) + end + + elseif p <= 5 then + if p < 5 then -- strings + I = i + 1 + addtoken("TK_STRING", read_string(r)) + break -- (continue) + end + r = match(z, "^%.%.?%.?", i) -- .|..|... dots + -- (fall through) + + else -- relational + r = match(z, "^%p=?", i) + -- (fall through) + end + end + I = i + #r + addtoken("TK_OP", r) -- for other symbols, fall through + break -- (continue) + end + + local r = sub(z, i, i) + if r ~= "" then + I = i + 1 + addtoken("TK_OP", r) -- other single-char tokens + break + end + addtoken("TK_EOS", "") -- end of stream, + return tok, seminfo, tokln -- exit here + + end--while inner + end--while outer +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/lparser.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/lparser.lua new file mode 100644 index 000000000..334243ea1 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/lparser.lua @@ -0,0 +1,1286 @@ +--------- +-- Lua 5.1+ parser written in Lua. +-- +-- This file is part of LuaSrcDiet, based on Yueliang material. +-- +-- **Notes:** +-- +-- * This is a version of the native 5.1.x parser from Yueliang 0.4.0, +-- with significant modifications to handle LuaSrcDiet's needs: +-- (1) needs pre-built token tables instead of a module.method, +-- (2) lparser.error is an optional error handler (from llex), +-- (3) not full parsing, currently fakes raw/unlexed constants, +-- (4) parser() returns globalinfo, localinfo tables. +-- * NO support for 'arg' vararg functions (LUA_COMPAT_VARARG). +-- * A lot of the parser is unused, but might later be useful for +-- full-on parsing and analysis. +-- * Relaxed parsing of statement to not require "break" to be the +-- last statement of block (Lua 5.2+). +-- * Added basic support for goto and label statements, i.e. parser +-- does not crash on them (Lua 5.2+). +---- +local fmt = string.format +local gmatch = string.gmatch +local pairs = pairs + +local M = {} + +--[[-------------------------------------------------------------------- +-- variable and data structure initialization +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- initialization: main variables +---------------------------------------------------------------------- + +local toklist, -- grammar-only token tables (token table, + seminfolist, -- semantic information table, line number + toklnlist, -- table, cross-reference table) + xreflist, + tpos, -- token position + + line, -- start line # for error messages + lastln, -- last line # for ambiguous syntax chk + tok, seminfo, ln, xref, -- token, semantic info, line + nameref, -- proper position of token + fs, -- current function state + top_fs, -- top-level function state + + globalinfo, -- global variable information table + globallookup, -- global variable name lookup table + localinfo, -- local variable information table + ilocalinfo, -- inactive locals (prior to activation) + ilocalrefs, -- corresponding references to activate + statinfo -- statements labeled by type + +-- forward references for local functions +local explist1, expr, block, exp1, body, chunk + +---------------------------------------------------------------------- +-- initialization: data structures +---------------------------------------------------------------------- + +local block_follow = {} -- lookahead check in chunk(), returnstat() +for v in gmatch("else elseif end until ", "%S+") do + block_follow[v] = true +end + +local binopr_left = {} -- binary operators, left priority +local binopr_right = {} -- binary operators, right priority +for op, lt, rt in gmatch([[ +{+ 6 6}{- 6 6}{* 7 7}{/ 7 7}{% 7 7} +{^ 10 9}{.. 5 4} +{~= 3 3}{== 3 3} +{< 3 3}{<= 3 3}{> 3 3}{>= 3 3} +{and 2 2}{or 1 1} +]], "{(%S+)%s(%d+)%s(%d+)}") do + binopr_left[op] = lt + 0 + binopr_right[op] = rt + 0 +end + +local unopr = { ["not"] = true, ["-"] = true, + ["#"] = true, } -- unary operators +local UNARY_PRIORITY = 8 -- priority for unary operators + +--[[-------------------------------------------------------------------- +-- support functions +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- formats error message and throws error (duplicated from llex) +-- * a simplified version, does not report what token was responsible +---------------------------------------------------------------------- + +local function errorline(s, line) + local e = M.error or error + e(fmt("(source):%d: %s", line or ln, s)) +end + +---------------------------------------------------------------------- +-- handles incoming token, semantic information pairs +-- * NOTE: 'nextt' is named 'next' originally +---------------------------------------------------------------------- + +-- reads in next token +local function nextt() + lastln = toklnlist[tpos] + tok, seminfo, ln, xref + = toklist[tpos], seminfolist[tpos], toklnlist[tpos], xreflist[tpos] + tpos = tpos + 1 +end + +-- peek at next token (single lookahead for table constructor) +local function lookahead() + return toklist[tpos] +end + +---------------------------------------------------------------------- +-- throws a syntax error, or if token expected is not there +---------------------------------------------------------------------- + +local function syntaxerror(msg) + if tok ~= "" and tok ~= "" then + if tok == "" then tok = seminfo end + tok = "'"..tok.."'" + end + errorline(msg.." near "..tok) +end + +local function error_expected(token) + syntaxerror("'"..token.."' expected") +end + +---------------------------------------------------------------------- +-- tests for a token, returns outcome +-- * return value changed to boolean +---------------------------------------------------------------------- + +local function testnext(c) + if tok == c then nextt(); return true end +end + +---------------------------------------------------------------------- +-- check for existence of a token, throws error if not found +---------------------------------------------------------------------- + +local function check(c) + if tok ~= c then error_expected(c) end +end + +---------------------------------------------------------------------- +-- verify existence of a token, then skip it +---------------------------------------------------------------------- + +local function checknext(c) + check(c); nextt() +end + +---------------------------------------------------------------------- +-- throws error if condition not matched +---------------------------------------------------------------------- + +local function check_condition(c, msg) + if not c then syntaxerror(msg) end +end + +---------------------------------------------------------------------- +-- verifies token conditions are met or else throw error +---------------------------------------------------------------------- + +local function check_match(what, who, where) + if not testnext(what) then + if where == ln then + error_expected(what) + else + syntaxerror("'"..what.."' expected (to close '"..who.."' at line "..where..")") + end + end +end + +---------------------------------------------------------------------- +-- expect that token is a name, consume it and return the name +---------------------------------------------------------------------- + +local function str_checkname() + check("") + local ts = seminfo + nameref = xref + nextt() + return ts +end + +--[[-------------------------------------------------------------------- +-- variable (global|local|upvalue) handling +-- * to track locals and globals, variable management code needed +-- * entry point is singlevar() for variable lookups +-- * lookup tables (bl.locallist) are maintained awkwardly in the basic +-- block data structures, PLUS the function data structure (this is +-- an inelegant hack, since bl is nil for the top level of a function) +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- register a local variable, create local variable object, set in +-- to-activate variable list +-- * used in new_localvarliteral(), parlist(), fornum(), forlist(), +-- localfunc(), localstat() +---------------------------------------------------------------------- + +local function new_localvar(name, special) + local bl = fs.bl + local locallist + -- locate locallist in current block object or function root object + if bl then + locallist = bl.locallist + else + locallist = fs.locallist + end + -- build local variable information object and set localinfo + local id = #localinfo + 1 + localinfo[id] = { -- new local variable object + name = name, -- local variable name + xref = { nameref }, -- xref, first value is declaration + decl = nameref, -- location of declaration, = xref[1] + } + if special or name == "_ENV" then -- "self" and "_ENV" must be not be changed + localinfo[id].is_special = true + end + -- this can override a local with the same name in the same scope + -- but first, keep it inactive until it gets activated + local i = #ilocalinfo + 1 + ilocalinfo[i] = id + ilocalrefs[i] = locallist +end + +---------------------------------------------------------------------- +-- actually activate the variables so that they are visible +-- * remember Lua semantics, e.g. RHS is evaluated first, then LHS +-- * used in parlist(), forbody(), localfunc(), localstat(), body() +---------------------------------------------------------------------- + +local function adjustlocalvars(nvars) + local sz = #ilocalinfo + -- i goes from left to right, in order of local allocation, because + -- of something like: local a,a,a = 1,2,3 which gives a = 3 + while nvars > 0 do + nvars = nvars - 1 + local i = sz - nvars + local id = ilocalinfo[i] -- local's id + local obj = localinfo[id] + local name = obj.name -- name of local + obj.act = xref -- set activation location + ilocalinfo[i] = nil + local locallist = ilocalrefs[i] -- ref to lookup table to update + ilocalrefs[i] = nil + local existing = locallist[name] -- if existing, remove old first! + if existing then -- do not overlap, set special + obj = localinfo[existing] -- form of rem, as -id + obj.rem = -id + end + locallist[name] = id -- activate, now visible to Lua + end +end + +---------------------------------------------------------------------- +-- remove (deactivate) variables in current scope (before scope exits) +-- * zap entire locallist tables since we are not allocating registers +-- * used in leaveblock(), close_func() +---------------------------------------------------------------------- + +local function removevars() + local bl = fs.bl + local locallist + -- locate locallist in current block object or function root object + if bl then + locallist = bl.locallist + else + locallist = fs.locallist + end + -- enumerate the local list at current scope and deactivate 'em + for _, id in pairs(locallist) do + local obj = localinfo[id] + obj.rem = xref -- set deactivation location + end +end + +---------------------------------------------------------------------- +-- creates a new local variable given a name +-- * skips internal locals (those starting with '('), so internal +-- locals never needs a corresponding adjustlocalvars() call +-- * special is true for "self" which must not be optimized +-- * used in fornum(), forlist(), parlist(), body() +---------------------------------------------------------------------- + +local function new_localvarliteral(name, special) + if name:sub(1, 1) == "(" then -- can skip internal locals + return + end + new_localvar(name, special) +end + +---------------------------------------------------------------------- +-- search the local variable namespace of the given fs for a match +-- * returns localinfo index +-- * used only in singlevaraux() +---------------------------------------------------------------------- + +local function searchvar(fs, n) + local bl = fs.bl + local locallist + if bl then + locallist = bl.locallist + while locallist do + if locallist[n] then return locallist[n] end -- found + bl = bl.prev + locallist = bl and bl.locallist + end + end + locallist = fs.locallist + return locallist[n] or -1 -- found or not found (-1) +end + +---------------------------------------------------------------------- +-- handle locals, globals and upvalues and related processing +-- * search mechanism is recursive, calls itself to search parents +-- * used only in singlevar() +---------------------------------------------------------------------- + +local function singlevaraux(fs, n, var) + if fs == nil then -- no more levels? + var.k = "VGLOBAL" -- default is global variable + return "VGLOBAL" + else + local v = searchvar(fs, n) -- look up at current level + if v >= 0 then + var.k = "VLOCAL" + var.id = v + -- codegen may need to deal with upvalue here + return "VLOCAL" + else -- not found at current level; try upper one + if singlevaraux(fs.prev, n, var) == "VGLOBAL" then + return "VGLOBAL" + end + -- else was LOCAL or UPVAL, handle here + var.k = "VUPVAL" -- upvalue in this level + return "VUPVAL" + end--if v + end--if fs +end + +---------------------------------------------------------------------- +-- consume a name token, creates a variable (global|local|upvalue) +-- * used in prefixexp(), funcname() +---------------------------------------------------------------------- + +local function singlevar(v) + local name = str_checkname() + singlevaraux(fs, name, v) + ------------------------------------------------------------------ + -- variable tracking + ------------------------------------------------------------------ + if v.k == "VGLOBAL" then + -- if global being accessed, keep track of it by creating an object + local id = globallookup[name] + if not id then + id = #globalinfo + 1 + globalinfo[id] = { -- new global variable object + name = name, -- global variable name + xref = { nameref }, -- xref, first value is declaration + } + globallookup[name] = id -- remember it + else + local obj = globalinfo[id].xref + obj[#obj + 1] = nameref -- add xref + end + else + -- local/upvalue is being accessed, keep track of it + local obj = localinfo[v.id].xref + obj[#obj + 1] = nameref -- add xref + end +end + +--[[-------------------------------------------------------------------- +-- state management functions with open/close pairs +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- enters a code unit, initializes elements +---------------------------------------------------------------------- + +local function enterblock(isbreakable) + local bl = {} -- per-block state + bl.isbreakable = isbreakable + bl.prev = fs.bl + bl.locallist = {} + fs.bl = bl +end + +---------------------------------------------------------------------- +-- leaves a code unit, close any upvalues +---------------------------------------------------------------------- + +local function leaveblock() + local bl = fs.bl + removevars() + fs.bl = bl.prev +end + +---------------------------------------------------------------------- +-- opening of a function +-- * top_fs is only for anchoring the top fs, so that parser() can +-- return it to the caller function along with useful output +-- * used in parser() and body() +---------------------------------------------------------------------- + +local function open_func() + local new_fs -- per-function state + if not fs then -- top_fs is created early + new_fs = top_fs + else + new_fs = {} + end + new_fs.prev = fs -- linked list of function states + new_fs.bl = nil + new_fs.locallist = {} + fs = new_fs +end + +---------------------------------------------------------------------- +-- closing of a function +-- * used in parser() and body() +---------------------------------------------------------------------- + +local function close_func() + removevars() + fs = fs.prev +end + +--[[-------------------------------------------------------------------- +-- other parsing functions +-- * for table constructor, parameter list, argument list +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- parse a function name suffix, for function call specifications +-- * used in primaryexp(), funcname() +---------------------------------------------------------------------- + +local function field(v) + -- field -> ['.' | ':'] NAME + nextt() -- skip the dot or colon + str_checkname() + v.k = "VINDEXED" +end + +---------------------------------------------------------------------- +-- parse a table indexing suffix, for constructors, expressions +-- * used in recfield(), primaryexp() +---------------------------------------------------------------------- + +local function yindex() + -- index -> '[' expr ']' + nextt() -- skip the '[' + expr({}) + checknext("]") +end + +---------------------------------------------------------------------- +-- parse a table record (hash) field +-- * used in constructor() +---------------------------------------------------------------------- + +local function recfield() + -- recfield -> (NAME | '['exp1']') = exp1 + if tok == "" then + str_checkname() + else-- tok == '[' + yindex() + end + checknext("=") + expr({}) +end + +---------------------------------------------------------------------- +-- parse a table list (array) field +-- * used in constructor() +---------------------------------------------------------------------- + +local function listfield(cc) + expr(cc.v) +end + +---------------------------------------------------------------------- +-- parse a table constructor +-- * used in funcargs(), simpleexp() +---------------------------------------------------------------------- + +local function constructor(t) + -- constructor -> '{' [ field { fieldsep field } [ fieldsep ] ] '}' + -- field -> recfield | listfield + -- fieldsep -> ',' | ';' + local line = ln + local cc = { + v = { k = "VVOID" }, + } + t.k = "VRELOCABLE" + checknext("{") + repeat + if tok == "}" then break end + -- closelistfield(cc) here + local c = tok + if c == "" then -- may be listfields or recfields + if lookahead() ~= "=" then -- look ahead: expression? + listfield(cc) + else + recfield() + end + elseif c == "[" then -- constructor_item -> recfield + recfield() + else -- constructor_part -> listfield + listfield(cc) + end + until not testnext(",") and not testnext(";") + check_match("}", "{", line) + -- lastlistfield(cc) here +end + +---------------------------------------------------------------------- +-- parse the arguments (parameters) of a function declaration +-- * used in body() +---------------------------------------------------------------------- + +local function parlist() + -- parlist -> [ param { ',' param } ] + local nparams = 0 + if tok ~= ")" then -- is 'parlist' not empty? + repeat + local c = tok + if c == "" then -- param -> NAME + new_localvar(str_checkname()) + nparams = nparams + 1 + elseif c == "..." then + nextt() + fs.is_vararg = true + else + syntaxerror(" or '...' expected") + end + until fs.is_vararg or not testnext(",") + end--if + adjustlocalvars(nparams) +end + +---------------------------------------------------------------------- +-- parse the parameters of a function call +-- * contrast with parlist(), used in function declarations +-- * used in primaryexp() +---------------------------------------------------------------------- + +local function funcargs(f) + local line = ln + local c = tok + if c == "(" then -- funcargs -> '(' [ explist1 ] ')' + if line ~= lastln then + syntaxerror("ambiguous syntax (function call x new statement)") + end + nextt() + if tok ~= ")" then -- arg list is not empty? + explist1() + end + check_match(")", "(", line) + elseif c == "{" then -- funcargs -> constructor + constructor({}) + elseif c == "" then -- funcargs -> STRING + nextt() -- must use 'seminfo' before 'next' + else + syntaxerror("function arguments expected") + return + end--if c + f.k = "VCALL" +end + +--[[-------------------------------------------------------------------- +-- mostly expression functions +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- parses an expression in parentheses or a single variable +-- * used in primaryexp() +---------------------------------------------------------------------- + +local function prefixexp(v) + -- prefixexp -> NAME | '(' expr ')' + local c = tok + if c == "(" then + local line = ln + nextt() + expr(v) + check_match(")", "(", line) + elseif c == "" then + singlevar(v) + else + syntaxerror("unexpected symbol") + end--if c +end + +---------------------------------------------------------------------- +-- parses a prefixexp (an expression in parentheses or a single +-- variable) or a function call specification +-- * used in simpleexp(), assignment(), expr_stat() +---------------------------------------------------------------------- + +local function primaryexp(v) + -- primaryexp -> + -- prefixexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } + prefixexp(v) + while true do + local c = tok + if c == "." then -- field + field(v) + elseif c == "[" then -- '[' exp1 ']' + yindex() + elseif c == ":" then -- ':' NAME funcargs + nextt() + str_checkname() + funcargs(v) + elseif c == "(" or c == "" or c == "{" then -- funcargs + funcargs(v) + else + return + end--if c + end--while +end + +---------------------------------------------------------------------- +-- parses general expression types, constants handled here +-- * used in subexpr() +---------------------------------------------------------------------- + +local function simpleexp(v) + -- simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | + -- constructor | FUNCTION body | primaryexp + local c = tok + if c == "" then + v.k = "VKNUM" + elseif c == "" then + v.k = "VK" + elseif c == "nil" then + v.k = "VNIL" + elseif c == "true" then + v.k = "VTRUE" + elseif c == "false" then + v.k = "VFALSE" + elseif c == "..." then -- vararg + check_condition(fs.is_vararg == true, + "cannot use '...' outside a vararg function"); + v.k = "VVARARG" + elseif c == "{" then -- constructor + constructor(v) + return + elseif c == "function" then + nextt() + body(false, ln) + return + else + primaryexp(v) + return + end--if c + nextt() +end + +------------------------------------------------------------------------ +-- Parse subexpressions. Includes handling of unary operators and binary +-- operators. A subexpr is given the rhs priority level of the operator +-- immediately left of it, if any (limit is -1 if none,) and if a binop +-- is found, limit is compared with the lhs priority level of the binop +-- in order to determine which executes first. +-- * recursively called +-- * used in expr() +------------------------------------------------------------------------ + +local function subexpr(v, limit) + -- subexpr -> (simpleexp | unop subexpr) { binop subexpr } + -- * where 'binop' is any binary operator with a priority + -- higher than 'limit' + local op = tok + local uop = unopr[op] + if uop then + nextt() + subexpr(v, UNARY_PRIORITY) + else + simpleexp(v) + end + -- expand while operators have priorities higher than 'limit' + op = tok + local binop = binopr_left[op] + while binop and binop > limit do + nextt() + -- read sub-expression with higher priority + op = subexpr({}, binopr_right[op]) -- next operator + binop = binopr_left[op] + end + return op -- return first untreated operator +end + +---------------------------------------------------------------------- +-- Expression parsing starts here. Function subexpr is entered with the +-- left operator (which is non-existent) priority of -1, which is lower +-- than all actual operators. Expr information is returned in parm v. +-- * used in cond(), explist1(), index(), recfield(), listfield(), +-- prefixexp(), while_stat(), exp1() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function expr(v) + -- expr -> subexpr + subexpr(v, 0) +end + +--[[-------------------------------------------------------------------- +-- third level parsing functions +----------------------------------------------------------------------]] + +------------------------------------------------------------------------ +-- parse a variable assignment sequence +-- * recursively called +-- * used in expr_stat() +------------------------------------------------------------------------ + +local function assignment(v) + local c = v.v.k + check_condition(c == "VLOCAL" or c == "VUPVAL" or c == "VGLOBAL" + or c == "VINDEXED", "syntax error") + if testnext(",") then -- assignment -> ',' primaryexp assignment + local nv = {} -- expdesc + nv.v = {} + primaryexp(nv.v) + -- lparser.c deals with some register usage conflict here + assignment(nv) + else -- assignment -> '=' explist1 + checknext("=") + explist1() + return -- avoid default + end +end + +---------------------------------------------------------------------- +-- parse a for loop body for both versions of the for loop +-- * used in fornum(), forlist() +---------------------------------------------------------------------- + +local function forbody(nvars) + -- forbody -> DO block + checknext("do") + enterblock(false) -- scope for declared variables + adjustlocalvars(nvars) + block() + leaveblock() -- end of scope for declared variables +end + +---------------------------------------------------------------------- +-- parse a numerical for loop, calls forbody() +-- * used in for_stat() +---------------------------------------------------------------------- + +local function fornum(varname) + -- fornum -> NAME = exp1, exp1 [, exp1] DO body + new_localvarliteral("(for index)") + new_localvarliteral("(for limit)") + new_localvarliteral("(for step)") + new_localvar(varname) + checknext("=") + exp1() -- initial value + checknext(",") + exp1() -- limit + if testnext(",") then + exp1() -- optional step + else + -- default step = 1 + end + forbody(1) +end + +---------------------------------------------------------------------- +-- parse a generic for loop, calls forbody() +-- * used in for_stat() +---------------------------------------------------------------------- + +local function forlist(indexname) + -- forlist -> NAME {, NAME} IN explist1 DO body + -- create control variables + new_localvarliteral("(for generator)") + new_localvarliteral("(for state)") + new_localvarliteral("(for control)") + -- create declared variables + new_localvar(indexname) + local nvars = 1 + while testnext(",") do + new_localvar(str_checkname()) + nvars = nvars + 1 + end + checknext("in") + explist1() + forbody(nvars) +end + +---------------------------------------------------------------------- +-- parse a function name specification +-- * used in func_stat() +---------------------------------------------------------------------- + +local function funcname(v) + -- funcname -> NAME {field} [':' NAME] + local needself = false + singlevar(v) + while tok == "." do + field(v) + end + if tok == ":" then + needself = true + field(v) + end + return needself +end + +---------------------------------------------------------------------- +-- parse the single expressions needed in numerical for loops +-- * used in fornum() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function exp1() + -- exp1 -> expr + expr({}) +end + +---------------------------------------------------------------------- +-- parse condition in a repeat statement or an if control structure +-- * used in repeat_stat(), test_then_block() +---------------------------------------------------------------------- + +local function cond() + -- cond -> expr + expr({}) -- read condition +end + +---------------------------------------------------------------------- +-- parse part of an if control structure, including the condition +-- * used in if_stat() +---------------------------------------------------------------------- + +local function test_then_block() + -- test_then_block -> [IF | ELSEIF] cond THEN block + nextt() -- skip IF or ELSEIF + cond() + checknext("then") + block() -- 'then' part +end + +---------------------------------------------------------------------- +-- parse a local function statement +-- * used in local_stat() +---------------------------------------------------------------------- + +local function localfunc() + -- localfunc -> NAME body + new_localvar(str_checkname()) + adjustlocalvars(1) + body(false, ln) +end + +---------------------------------------------------------------------- +-- parse a local variable declaration statement +-- * used in local_stat() +---------------------------------------------------------------------- + +local function localstat() + -- localstat -> NAME {',' NAME} ['=' explist1] + local nvars = 0 + repeat + new_localvar(str_checkname()) + nvars = nvars + 1 + until not testnext(",") + if testnext("=") then + explist1() + else + -- VVOID + end + adjustlocalvars(nvars) +end + +---------------------------------------------------------------------- +-- parse a list of comma-separated expressions +-- * used in return_stat(), localstat(), funcargs(), assignment(), +-- forlist() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function explist1() + -- explist1 -> expr { ',' expr } + local e = {} + expr(e) + while testnext(",") do + expr(e) + end +end + +---------------------------------------------------------------------- +-- parse function declaration body +-- * used in simpleexp(), localfunc(), func_stat() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function body(needself, line) + -- body -> '(' parlist ')' chunk END + open_func() + checknext("(") + if needself then + new_localvarliteral("self", true) + adjustlocalvars(1) + end + parlist() + checknext(")") + chunk() + check_match("end", "function", line) + close_func() +end + +---------------------------------------------------------------------- +-- parse a code block or unit +-- * used in do_stat(), while_stat(), forbody(), test_then_block(), +-- if_stat() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function block() + -- block -> chunk + enterblock(false) + chunk() + leaveblock() +end + +--[[-------------------------------------------------------------------- +-- second level parsing functions, all with '_stat' suffix +-- * since they are called via a table lookup, they cannot be local +-- functions (a lookup table of local functions might be smaller...) +-- * stat() -> *_stat() +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- initial parsing for a for loop, calls fornum() or forlist() +-- * removed 'line' parameter (used to set debug information only) +-- * used in stat() +---------------------------------------------------------------------- + +local function for_stat() + -- stat -> for_stat -> FOR (fornum | forlist) END + local line = line + enterblock(true) -- scope for loop and control variables + nextt() -- skip 'for' + local varname = str_checkname() -- first variable name + local c = tok + if c == "=" then + fornum(varname) + elseif c == "," or c == "in" then + forlist(varname) + else + syntaxerror("'=' or 'in' expected") + end + check_match("end", "for", line) + leaveblock() -- loop scope (`break' jumps to this point) +end + +---------------------------------------------------------------------- +-- parse a while-do control structure, body processed by block() +-- * used in stat() +---------------------------------------------------------------------- + +local function while_stat() + -- stat -> while_stat -> WHILE cond DO block END + local line = line + nextt() -- skip WHILE + cond() -- parse condition + enterblock(true) + checknext("do") + block() + check_match("end", "while", line) + leaveblock() +end + +---------------------------------------------------------------------- +-- parse a repeat-until control structure, body parsed by chunk() +-- * originally, repeatstat() calls breakstat() too if there is an +-- upvalue in the scope block; nothing is actually lexed, it is +-- actually the common code in breakstat() for closing of upvalues +-- * used in stat() +---------------------------------------------------------------------- + +local function repeat_stat() + -- stat -> repeat_stat -> REPEAT block UNTIL cond + local line = line + enterblock(true) -- loop block + enterblock(false) -- scope block + nextt() -- skip REPEAT + chunk() + check_match("until", "repeat", line) + cond() + -- close upvalues at scope level below + leaveblock() -- finish scope + leaveblock() -- finish loop +end + +---------------------------------------------------------------------- +-- parse an if control structure +-- * used in stat() +---------------------------------------------------------------------- + +local function if_stat() + -- stat -> if_stat -> IF cond THEN block + -- {ELSEIF cond THEN block} [ELSE block] END + local line = line + test_then_block() -- IF cond THEN block + while tok == "elseif" do + test_then_block() -- ELSEIF cond THEN block + end + if tok == "else" then + nextt() -- skip ELSE + block() -- 'else' part + end + check_match("end", "if", line) +end + +---------------------------------------------------------------------- +-- parse a return statement +-- * used in stat() +---------------------------------------------------------------------- + +local function return_stat() + -- stat -> return_stat -> RETURN explist + nextt() -- skip RETURN + local c = tok + if block_follow[c] or c == ";" then + -- return no values + else + explist1() -- optional return values + end +end + +---------------------------------------------------------------------- +-- parse a break statement +-- * used in stat() +---------------------------------------------------------------------- + +local function break_stat() + -- stat -> break_stat -> BREAK + local bl = fs.bl + nextt() -- skip BREAK + while bl and not bl.isbreakable do -- find a breakable block + bl = bl.prev + end + if not bl then + syntaxerror("no loop to break") + end +end + +---------------------------------------------------------------------- +-- parse a label statement +-- * this function has been added later, it just parses label statement +-- without any validation! +-- * used in stat() +---------------------------------------------------------------------- + +local function label_stat() + -- stat -> label_stat -> '::' NAME '::' + nextt() -- skip '::' + str_checkname() + checknext("::") +end + +---------------------------------------------------------------------- +-- parse a goto statement +-- * this function has been added later, it just parses goto statement +-- without any validation! +-- * used in stat() +---------------------------------------------------------------------- + +local function goto_stat() + -- stat -> goto_stat -> GOTO NAME + nextt() -- skip GOTO + str_checkname() +end + +---------------------------------------------------------------------- +-- parse a function call with no returns or an assignment statement +-- * the struct with .prev is used for name searching in lparse.c, +-- so it is retained for now; present in assignment() also +-- * used in stat() +---------------------------------------------------------------------- + +local function expr_stat() + local id = tpos - 1 + -- stat -> expr_stat -> func | assignment + local v = { v = {} } + primaryexp(v.v) + if v.v.k == "VCALL" then -- stat -> func + -- call statement uses no results + statinfo[id] = "call" + else -- stat -> assignment + v.prev = nil + assignment(v) + statinfo[id] = "assign" + end +end + +---------------------------------------------------------------------- +-- parse a function statement +-- * used in stat() +---------------------------------------------------------------------- + +local function function_stat() + -- stat -> function_stat -> FUNCTION funcname body + local line = line + nextt() -- skip FUNCTION + local needself = funcname({}) + body(needself, line) +end + +---------------------------------------------------------------------- +-- parse a simple block enclosed by a DO..END pair +-- * used in stat() +---------------------------------------------------------------------- + +local function do_stat() + -- stat -> do_stat -> DO block END + local line = line + nextt() -- skip DO + block() + check_match("end", "do", line) +end + +---------------------------------------------------------------------- +-- parse a statement starting with LOCAL +-- * used in stat() +---------------------------------------------------------------------- + +local function local_stat() + -- stat -> local_stat -> LOCAL FUNCTION localfunc + -- -> LOCAL localstat + nextt() -- skip LOCAL + if testnext("function") then -- local function? + localfunc() + else + localstat() + end +end + +--[[-------------------------------------------------------------------- +-- main functions, top level parsing functions +-- * accessible functions are: init(lexer), parser() +-- * [entry] -> parser() -> chunk() -> stat() +----------------------------------------------------------------------]] + +---------------------------------------------------------------------- +-- initial parsing for statements, calls '_stat' suffixed functions +-- * used in chunk() +---------------------------------------------------------------------- + +local stat_call = { -- lookup for calls in stat() + ["if"] = if_stat, + ["while"] = while_stat, + ["do"] = do_stat, + ["for"] = for_stat, + ["repeat"] = repeat_stat, + ["function"] = function_stat, + ["local"] = local_stat, + ["return"] = return_stat, + ["break"] = break_stat, + ["goto"] = goto_stat, + ["::"] = label_stat, +} + +local function stat() + -- stat -> if_stat while_stat do_stat for_stat repeat_stat + -- function_stat local_stat return_stat break_stat + -- expr_stat + line = ln -- may be needed for error messages + local c = tok + local fn = stat_call[c] + -- handles: if while do for repeat function local return break + if fn then + statinfo[tpos - 1] = c + fn() + -- return must be last statement + if c == "return" then return true end + else + expr_stat() + end + return false +end + +---------------------------------------------------------------------- +-- parse a chunk, which consists of a bunch of statements +-- * used in parser(), body(), block(), repeat_stat() +---------------------------------------------------------------------- + +-- this is a forward-referenced local +function chunk() + -- chunk -> { stat [';'] } + local islast = false + while not islast and not block_follow[tok] do + islast = stat() + testnext(";") + end +end + +---------------------------------------------------------------------- +-- initialization function +---------------------------------------------------------------------- + +local function init(tokorig, seminfoorig, toklnorig) + tpos = 1 -- token position + top_fs = {} -- reset top level function state + ------------------------------------------------------------------ + -- set up grammar-only token tables; impedance-matching... + -- note that constants returned by the lexer is source-level, so + -- for now, fake(!) constant tokens (TK_NUMBER|TK_STRING|TK_LSTRING) + ------------------------------------------------------------------ + local j = 1 + toklist, seminfolist, toklnlist, xreflist = {}, {}, {}, {} + for i = 1, #tokorig do + local tok = tokorig[i] + local yep = true + if tok == "TK_KEYWORD" or tok == "TK_OP" then + tok = seminfoorig[i] + elseif tok == "TK_NAME" then + tok = "" + seminfolist[j] = seminfoorig[i] + elseif tok == "TK_NUMBER" then + tok = "" + seminfolist[j] = 0 -- fake! + elseif tok == "TK_STRING" or tok == "TK_LSTRING" then + tok = "" + seminfolist[j] = "" -- fake! + elseif tok == "TK_EOS" then + tok = "" + else + -- non-grammar tokens; ignore them + yep = false + end + if yep then -- set rest of the information + toklist[j] = tok + toklnlist[j] = toklnorig[i] + xreflist[j] = i + j = j + 1 + end + end--for + ------------------------------------------------------------------ + -- initialize data structures for variable tracking + ------------------------------------------------------------------ + globalinfo, globallookup, localinfo = {}, {}, {} + ilocalinfo, ilocalrefs = {}, {} + statinfo = {} -- experimental +end + +---------------------------------------------------------------------- +-- performs parsing, returns parsed data structure +---------------------------------------------------------------------- + +function M.parse(tokens, seminfo, tokens_ln) + init(tokens, seminfo, tokens_ln) + + open_func() + fs.is_vararg = true -- main func. is always vararg + nextt() -- read first token + chunk() + check("") + close_func() + return { -- return everything + globalinfo = globalinfo, + localinfo = localinfo, + statinfo = statinfo, + toklist = toklist, + seminfolist = seminfolist, + toklnlist = toklnlist, + xreflist = xreflist, + } +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/optlex.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/optlex.lua new file mode 100644 index 000000000..70c06bb44 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/optlex.lua @@ -0,0 +1,852 @@ +--------- +-- This module does lexer-based optimizations. +-- +-- **Notes:** +-- +-- * TODO: General string delimiter conversion optimizer. +-- * TODO: (numbers) warn if overly significant digit. +---- +local char = string.char +local find = string.find +local match = string.match +local rep = string.rep +local sub = string.sub +local tonumber = tonumber +local tostring = tostring + +local print -- set in optimize() + +local M = {} + +-- error function, can override by setting own function into module +M.error = error + +M.warn = {} -- table for warning flags + +local stoks, sinfos, stoklns -- source lists + +local is_realtoken = { -- significant (grammar) tokens + TK_KEYWORD = true, + TK_NAME = true, + TK_NUMBER = true, + TK_STRING = true, + TK_LSTRING = true, + TK_OP = true, + TK_EOS = true, +} +local is_faketoken = { -- whitespace (non-grammar) tokens + TK_COMMENT = true, + TK_LCOMMENT = true, + TK_EOL = true, + TK_SPACE = true, +} + +local opt_details -- for extra information + +--- Returns true if current token is at the start of a line. +-- +-- It skips over deleted tokens via recursion. +-- +-- @tparam int i +-- @treturn bool +local function atlinestart(i) + local tok = stoks[i - 1] + if i <= 1 or tok == "TK_EOL" then + return true + elseif tok == "" then + return atlinestart(i - 1) + end + return false +end + +--- Returns true if current token is at the end of a line. +-- +-- It skips over deleted tokens via recursion. +-- +-- @tparam int i +-- @treturn bool +local function atlineend(i) + local tok = stoks[i + 1] + if i >= #stoks or tok == "TK_EOL" or tok == "TK_EOS" then + return true + elseif tok == "" then + return atlineend(i + 1) + end + return false +end + +--- Counts comment EOLs inside a long comment. +-- +-- In order to keep line numbering, EOLs need to be reinserted. +-- +-- @tparam string lcomment +-- @treturn int +local function commenteols(lcomment) + local sep = #match(lcomment, "^%-%-%[=*%[") + local z = sub(lcomment, sep + 1, -(sep - 1)) -- remove delims + local i, c = 1, 0 + while true do + local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i) + if not p then break end -- if no matches, done + i = p + 1 + c = c + 1 + if #s > 0 and r ~= s then -- skip CRLF or LFCR + i = i + 1 + end + end + return c +end + +--- Compares two tokens (i, j) and returns the whitespace required. +-- +-- See documentation for a reference table of interactions. +-- +-- Only two grammar/real tokens are being considered: +-- +-- * if `""`, no separation is needed, +-- * if `" "`, then at least one whitespace (or EOL) is required. +-- +-- Note: This doesn't work at the start or the end or for EOS! +-- +-- @tparam int i +-- @tparam int j +-- @treturn string +local function checkpair(i, j) + local t1, t2 = stoks[i], stoks[j] + + if t1 == "TK_STRING" or t1 == "TK_LSTRING" or + t2 == "TK_STRING" or t2 == "TK_LSTRING" then + return "" + + elseif t1 == "TK_OP" or t2 == "TK_OP" then + if (t1 == "TK_OP" and (t2 == "TK_KEYWORD" or t2 == "TK_NAME")) or + (t2 == "TK_OP" and (t1 == "TK_KEYWORD" or t1 == "TK_NAME")) then + return "" + end + if t1 == "TK_OP" and t2 == "TK_OP" then + -- for TK_OP/TK_OP pairs, see notes in technotes.txt + local op, op2 = sinfos[i], sinfos[j] + if (match(op, "^%.%.?$") and match(op2, "^%.")) or + (match(op, "^[~=<>]$") and op2 == "=") or + (op == "[" and (op2 == "[" or op2 == "=")) then + return " " + end + return "" + end + -- "TK_OP" + "TK_NUMBER" case + local op = sinfos[i] + if t2 == "TK_OP" then op = sinfos[j] end + if match(op, "^%.%.?%.?$") then + return " " + end + return "" + + else-- "TK_KEYWORD" | "TK_NAME" | "TK_NUMBER" then + return " " + + end +end + +--- Repack tokens, removing deletions caused by optimization process. +local function repack_tokens() + local dtoks, dinfos, dtoklns = {}, {}, {} + local j = 1 + for i = 1, #stoks do + local tok = stoks[i] + if tok ~= "" then + dtoks[j], dinfos[j], dtoklns[j] = tok, sinfos[i], stoklns[i] + j = j + 1 + end + end + stoks, sinfos, stoklns = dtoks, dinfos, dtoklns +end + +--- Does number optimization. +-- +-- Optimization using string formatting functions is one way of doing this, +-- but here, we consider all cases and handle them separately (possibly an +-- idiotic approach...). +-- +-- Scientific notation being generated is not in canonical form, this may or +-- may not be a bad thing. +-- +-- Note: Intermediate portions need to fit into a normal number range. +-- +-- Optimizations can be divided based on number patterns: +-- +-- * hexadecimal: +-- (1) no need to remove leading zeros, just skip to (2) +-- (2) convert to integer if size equal or smaller +-- * change if equal size -> lose the 'x' to reduce entropy +-- (3) number is then processed as an integer +-- (4) note: does not make 0[xX] consistent +-- * integer: +-- (1) reduce useless fractional part, if present, e.g. 123.000 -> 123. +-- (2) remove leading zeros, e.g. 000123 +-- * float: +-- (1) split into digits dot digits +-- (2) if no integer portion, take as zero (can omit later) +-- (3) handle degenerate .000 case, after which the fractional part +-- must be non-zero (if zero, it's matched as float .0) +-- (4) remove trailing zeros for fractional portion +-- (5) p.q where p > 0 and q > 0 cannot be shortened any more +-- (6) otherwise p == 0 and the form is .q, e.g. .000123 +-- (7) if scientific shorter, convert, e.g. .000123 -> 123e-6 +-- * scientific: +-- (1) split into (digits dot digits) [eE] ([+-] digits) +-- (2) if significand is zero, just use .0 +-- (3) remove leading zeros for significand +-- (4) shift out trailing zeros for significand +-- (5) examine exponent and determine which format is best: +-- number with fraction, or scientific +-- +-- Note: Number with fraction and scientific number is never converted +-- to integer, because Lua 5.3 distinguishes between integers and floats. +-- +-- +-- @tparam int i +local function do_number(i) + local before = sinfos[i] -- 'before' + local z = before -- working representation + local y -- 'after', if better + -------------------------------------------------------------------- + if match(z, "^0[xX]") then -- hexadecimal number + local v = tostring(tonumber(z)) + if #v <= #z then + z = v -- change to integer, AND continue + else + return -- no change; stick to hex + end + end + + if match(z, "^%d+$") then -- integer + if tonumber(z) > 0 then + y = match(z, "^0*([1-9]%d*)$") -- remove leading zeros + else + y = "0" -- basic zero + end + + elseif not match(z, "[eE]") then -- float + local p, q = match(z, "^(%d*)%.(%d*)$") -- split + if p == "" then p = 0 end -- int part zero + if q == "" then q = "0" end -- fraction part zero + if tonumber(q) == 0 and p == 0 then + y = ".0" -- degenerate .000 to .0 + else + -- now, q > 0 holds and p is a number + local zeros_cnt = #match(q, "0*$") -- remove trailing zeros + if zeros_cnt > 0 then + q = sub(q, 1, #q - zeros_cnt) + end + -- if p > 0, nothing else we can do to simplify p.q case + if tonumber(p) > 0 then + y = p.."."..q + else + y = "."..q -- tentative, e.g. .000123 + local v = #match(q, "^0*") -- # leading spaces + local w = #q - v -- # significant digits + local nv = tostring(#q) + -- e.g. compare 123e-6 versus .000123 + if w + 2 + #nv < 1 + #q then + y = sub(q, -w).."e-"..nv + end + end + end + + else -- scientific number + local sig, ex = match(z, "^([^eE]+)[eE]([%+%-]?%d+)$") + ex = tonumber(ex) + -- if got ".", shift out fractional portion of significand + local p, q = match(sig, "^(%d*)%.(%d*)$") + if p then + ex = ex - #q + sig = p..q + end + if tonumber(sig) == 0 then + y = ".0" -- basic float zero + else + local v = #match(sig, "^0*") -- remove leading zeros + sig = sub(sig, v + 1) + v = #match(sig, "0*$") -- shift out trailing zeros + if v > 0 then + sig = sub(sig, 1, #sig - v) + ex = ex + v + end + -- examine exponent and determine which format is best + local nex = tostring(ex) + if ex >= 0 and (ex <= 1 + #nex) then -- a float + y = sig..rep("0", ex).."." + elseif ex < 0 and (ex >= -#sig) then -- fraction, e.g. .123 + v = #sig + ex + y = sub(sig, 1, v).."."..sub(sig, v + 1) + elseif ex < 0 and (#nex >= -ex - #sig) then + -- e.g. compare 1234e-5 versus .01234 + -- gives: #sig + 1 + #nex >= 1 + (-ex - #sig) + #sig + -- -> #nex >= -ex - #sig + v = -ex - #sig + y = "."..rep("0", v)..sig + else -- non-canonical scientific representation + y = sig.."e"..ex + end + end--if sig + end + + if y and y ~= sinfos[i] then + if opt_details then + print(" (line "..stoklns[i]..") "..sinfos[i].." -> "..y) + opt_details = opt_details + 1 + end + sinfos[i] = y + end +end + +--- Does string optimization. +-- +-- Note: It works on well-formed strings only! +-- +-- Optimizations on characters can be summarized as follows: +-- +-- \a\b\f\n\r\t\v -- no change +-- \\ -- no change +-- \"\' -- depends on delim, other can remove \ +-- \[\] -- remove \ +-- \ -- general escape, remove \ (Lua 5.1 only) +-- \ -- normalize the EOL only +-- \ddd -- if \a\b\f\n\r\t\v, change to latter +-- if other < ascii 32, keep ddd but zap leading zeros +-- but cannot have following digits +-- if >= ascii 32, translate it into the literal, then also +-- do escapes for \\,\",\' cases +-- -- no change +-- +-- Switch delimiters if string becomes shorter. +-- +-- @tparam int I +local function do_string(I) + local info = sinfos[I] + local delim = sub(info, 1, 1) -- delimiter used + local ndelim = (delim == "'") and '"' or "'" -- opposite " <-> ' + local z = sub(info, 2, -2) -- actual string + local i = 1 + local c_delim, c_ndelim = 0, 0 -- "/' counts + + while i <= #z do + local c = sub(z, i, i) + + if c == "\\" then -- escaped stuff + local j = i + 1 + local d = sub(z, j, j) + local p = find("abfnrtv\\\n\r\"\'0123456789", d, 1, true) + + if not p then -- \ -- remove \ (Lua 5.1 only) + z = sub(z, 1, i - 1)..sub(z, j) + i = i + 1 + + elseif p <= 8 then -- \a\b\f\n\r\t\v\\ + i = i + 2 -- no change + + elseif p <= 10 then -- \ -- normalize EOL + local eol = sub(z, j, j + 1) + if eol == "\r\n" or eol == "\n\r" then + z = sub(z, 1, i).."\n"..sub(z, j + 2) + elseif p == 10 then -- \r case + z = sub(z, 1, i).."\n"..sub(z, j + 1) + end + i = i + 2 + + elseif p <= 12 then -- \"\' -- remove \ for ndelim + if d == delim then + c_delim = c_delim + 1 + i = i + 2 + else + c_ndelim = c_ndelim + 1 + z = sub(z, 1, i - 1)..sub(z, j) + i = i + 1 + end + + else -- \ddd -- various steps + local s = match(z, "^(%d%d?%d?)", j) + j = i + 1 + #s -- skip to location + local cv = tonumber(s) + local cc = char(cv) + p = find("\a\b\f\n\r\t\v", cc, 1, true) + if p then -- special escapes + s = "\\"..sub("abfnrtv", p, p) + elseif cv < 32 then -- normalized \ddd + if match(sub(z, j, j), "%d") then + -- if a digit follows, \ddd cannot be shortened + s = "\\"..s + else + s = "\\"..cv + end + elseif cc == delim then -- \ + s = "\\"..cc + c_delim = c_delim + 1 + elseif cc == "\\" then -- \\ + s = "\\\\" + else -- literal character + s = cc + if cc == ndelim then + c_ndelim = c_ndelim + 1 + end + end + z = sub(z, 1, i - 1)..s..sub(z, j) + i = i + #s + + end--if p + + else-- c ~= "\\" -- -- no change + i = i + 1 + if c == ndelim then -- count ndelim, for switching delimiters + c_ndelim = c_ndelim + 1 + end + + end--if c + end--while + + -- Switching delimiters, a long-winded derivation: + -- (1) delim takes 2+2*c_delim bytes, ndelim takes c_ndelim bytes + -- (2) delim becomes c_delim bytes, ndelim becomes 2+2*c_ndelim bytes + -- simplifying the condition (1)>(2) --> c_delim > c_ndelim + if c_delim > c_ndelim then + i = 1 + while i <= #z do + local p, _, r = find(z, "([\'\"])", i) + if not p then break end + if r == delim then -- \ -> + z = sub(z, 1, p - 2)..sub(z, p) + i = p + else-- r == ndelim -- -> \ + z = sub(z, 1, p - 1).."\\"..sub(z, p) + i = p + 2 + end + end--while + delim = ndelim -- actually change delimiters + end + + z = delim..z..delim + if z ~= sinfos[I] then + if opt_details then + print(" (line "..stoklns[I]..") "..sinfos[I].." -> "..z) + opt_details = opt_details + 1 + end + sinfos[I] = z + end +end + +--- Does long string optimization. +-- +-- * remove first optional newline +-- * normalize embedded newlines +-- * reduce '=' separators in delimiters if possible +-- +-- Note: warning flagged if trailing whitespace found, not trimmed. +-- +-- @tparam int I +local function do_lstring(I) + local info = sinfos[I] + local delim1 = match(info, "^%[=*%[") -- cut out delimiters + local sep = #delim1 + local delim2 = sub(info, -sep, -1) + local z = sub(info, sep + 1, -(sep + 1)) -- lstring without delims + local y = "" + local i = 1 + + while true do + local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i) + -- deal with a single line + local ln + if not p then + ln = sub(z, i) + elseif p >= i then + ln = sub(z, i, p - 1) + end + if ln ~= "" then + -- flag a warning if there are trailing spaces, won't optimize! + if match(ln, "%s+$") then + M.warn.LSTRING = "trailing whitespace in long string near line "..stoklns[I] + end + y = y..ln + end + if not p then -- done if no more EOLs + break + end + -- deal with line endings, normalize them + i = p + 1 + if p then + if #s > 0 and r ~= s then -- skip CRLF or LFCR + i = i + 1 + end + -- skip first newline, which can be safely deleted + if not(i == 1 and i == p) then + y = y.."\n" + end + end + end--while + + -- handle possible deletion of one or more '=' separators + if sep >= 3 then + local chk, okay = sep - 1 + -- loop to test ending delimiter with less of '=' down to zero + while chk >= 2 do + local delim = "%]"..rep("=", chk - 2).."%]" + if not match(y, delim) then okay = chk end + chk = chk - 1 + end + if okay then -- change delimiters + sep = rep("=", okay - 2) + delim1, delim2 = "["..sep.."[", "]"..sep.."]" + end + end + + sinfos[I] = delim1..y..delim2 +end + +--- Does long comment optimization. +-- +-- * trim trailing whitespace +-- * normalize embedded newlines +-- * reduce '=' separators in delimiters if possible +-- +-- Note: It does not remove first optional newline. +-- +-- @tparam int I +local function do_lcomment(I) + local info = sinfos[I] + local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters + local sep = #delim1 + local delim2 = sub(info, -(sep - 2), -1) + local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims + local y = "" + local i = 1 + + while true do + local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i) + -- deal with a single line, extract and check trailing whitespace + local ln + if not p then + ln = sub(z, i) + elseif p >= i then + ln = sub(z, i, p - 1) + end + if ln ~= "" then + -- trim trailing whitespace if non-empty line + local ws = match(ln, "%s*$") + if #ws > 0 then ln = sub(ln, 1, -(ws + 1)) end + y = y..ln + end + if not p then -- done if no more EOLs + break + end + -- deal with line endings, normalize them + i = p + 1 + if p then + if #s > 0 and r ~= s then -- skip CRLF or LFCR + i = i + 1 + end + y = y.."\n" + end + end--while + + -- handle possible deletion of one or more '=' separators + sep = sep - 2 + if sep >= 3 then + local chk, okay = sep - 1 + -- loop to test ending delimiter with less of '=' down to zero + while chk >= 2 do + local delim = "%]"..rep("=", chk - 2).."%]" + if not match(y, delim) then okay = chk end + chk = chk - 1 + end + if okay then -- change delimiters + sep = rep("=", okay - 2) + delim1, delim2 = "--["..sep.."[", "]"..sep.."]" + end + end + + sinfos[I] = delim1..y..delim2 +end + +--- Does short comment optimization. +-- +-- * trim trailing whitespace +-- +-- @tparam int i +local function do_comment(i) + local info = sinfos[i] + local ws = match(info, "%s*$") -- just look from end of string + if #ws > 0 then + info = sub(info, 1, -(ws + 1)) -- trim trailing whitespace + end + sinfos[i] = info +end + +--- Returns true if string found in long comment. +-- +-- This is a feature to keep copyright or license texts. +-- +-- @tparam bool opt_keep +-- @tparam string info +-- @treturn bool +local function keep_lcomment(opt_keep, info) + if not opt_keep then return false end -- option not set + local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters + local sep = #delim1 + local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims + if find(z, opt_keep, 1, true) then -- try to match + return true + end +end + +--- The main entry point. +-- +-- * currently, lexer processing has 2 passes +-- * processing is done on a line-oriented basis, which is easier to +-- grok due to the next point... +-- * since there are various options that can be enabled or disabled, +-- processing is a little messy or convoluted +-- +-- @tparam {[string]=bool,...} option +-- @tparam {string,...} toklist +-- @tparam {string,...} semlist +-- @tparam {int,...} toklnlist +-- @treturn {string,...} toklist +-- @treturn {string,...} semlist +-- @treturn {int,...} toklnlist +function M.optimize(option, toklist, semlist, toklnlist) + -- Set option flags. + local opt_comments = option["opt-comments"] + local opt_whitespace = option["opt-whitespace"] + local opt_emptylines = option["opt-emptylines"] + local opt_eols = option["opt-eols"] + local opt_strings = option["opt-strings"] + local opt_numbers = option["opt-numbers"] + local opt_x = option["opt-experimental"] + local opt_keep = option.KEEP + opt_details = option.DETAILS and 0 -- upvalues for details display + print = M.print or _G.print + if opt_eols then -- forced settings, otherwise won't work properly + opt_comments = true + opt_whitespace = true + opt_emptylines = true + elseif opt_x then + opt_whitespace = true + end + + -- Variable initialization. + stoks, sinfos, stoklns -- set source lists + = toklist, semlist, toklnlist + local i = 1 -- token position + local tok, info -- current token + local prev -- position of last grammar token + -- on same line (for TK_SPACE stuff) + + -- Changes a token, info pair. + local function settoken(tok, info, I) --luacheck: ignore 431 + I = I or i + stoks[I] = tok or "" + sinfos[I] = info or "" + end + + -- Experimental optimization for ';' operator. + if opt_x then + while true do + tok, info = stoks[i], sinfos[i] + if tok == "TK_EOS" then -- end of stream/pass + break + elseif tok == "TK_OP" and info == ";" then + -- ';' operator found, since it is entirely optional, set it + -- as a space to let whitespace optimization do the rest + settoken("TK_SPACE", " ") + end + i = i + 1 + end + repack_tokens() + end + + -- Processing loop (PASS 1) + i = 1 + while true do + tok, info = stoks[i], sinfos[i] + + local atstart = atlinestart(i) -- set line begin flag + if atstart then prev = nil end + + if tok == "TK_EOS" then -- end of stream/pass + break + + elseif tok == "TK_KEYWORD" or -- keywords, identifiers, + tok == "TK_NAME" or -- operators + tok == "TK_OP" then + -- TK_KEYWORD and TK_OP can't be optimized without a big + -- optimization framework; it would be more of an optimizing + -- compiler, not a source code compressor + -- TK_NAME that are locals needs parser to analyze/optimize + prev = i + + elseif tok == "TK_NUMBER" then -- numbers + if opt_numbers then + do_number(i) -- optimize + end + prev = i + + elseif tok == "TK_STRING" or -- strings, long strings + tok == "TK_LSTRING" then + if opt_strings then + if tok == "TK_STRING" then + do_string(i) -- optimize + else + do_lstring(i) -- optimize + end + end + prev = i + + elseif tok == "TK_COMMENT" then -- short comments + if opt_comments then + if i == 1 and sub(info, 1, 1) == "#" then + -- keep shbang comment, trim whitespace + do_comment(i) + else + -- safe to delete, as a TK_EOL (or TK_EOS) always follows + settoken() -- remove entirely + end + elseif opt_whitespace then -- trim whitespace only + do_comment(i) + end + + elseif tok == "TK_LCOMMENT" then -- long comments + if keep_lcomment(opt_keep, info) then + -- if --keep, we keep a long comment if is found; + -- this is a feature to keep copyright or license texts + if opt_whitespace then -- trim whitespace only + do_lcomment(i) + end + prev = i + elseif opt_comments then + local eols = commenteols(info) + + -- prepare opt_emptylines case first, if a disposable token + -- follows, current one is safe to dump, else keep a space; + -- it is implied that the operation is safe for '-', because + -- current is a TK_LCOMMENT, and must be separate from a '-' + if is_faketoken[stoks[i + 1]] then + settoken() -- remove entirely + tok = "" + else + settoken("TK_SPACE", " ") + end + + -- if there are embedded EOLs to keep and opt_emptylines is + -- disabled, then switch the token into one or more EOLs + if not opt_emptylines and eols > 0 then + settoken("TK_EOL", rep("\n", eols)) + end + + -- if optimizing whitespaces, force reinterpretation of the + -- token to give a chance for the space to be optimized away + if opt_whitespace and tok ~= "" then + i = i - 1 -- to reinterpret + end + else -- disabled case + if opt_whitespace then -- trim whitespace only + do_lcomment(i) + end + prev = i + end + + elseif tok == "TK_EOL" then -- line endings + if atstart and opt_emptylines then + settoken() -- remove entirely + elseif info == "\r\n" or info == "\n\r" then + -- normalize the rest of the EOLs for CRLF/LFCR only + -- (note that TK_LCOMMENT can change into several EOLs) + settoken("TK_EOL", "\n") + end + + elseif tok == "TK_SPACE" then -- whitespace + if opt_whitespace then + if atstart or atlineend(i) then + -- delete leading and trailing whitespace + settoken() -- remove entirely + else + + -- at this point, since leading whitespace have been removed, + -- there should be a either a real token or a TK_LCOMMENT + -- prior to hitting this whitespace; the TK_LCOMMENT case + -- only happens if opt_comments is disabled; so prev ~= nil + local ptok = stoks[prev] + if ptok == "TK_LCOMMENT" then + -- previous TK_LCOMMENT can abut with anything + settoken() -- remove entirely + else + -- prev must be a grammar token; consecutive TK_SPACE + -- tokens is impossible when optimizing whitespace + local ntok = stoks[i + 1] + if is_faketoken[ntok] then + -- handle special case where a '-' cannot abut with + -- either a short comment or a long comment + if (ntok == "TK_COMMENT" or ntok == "TK_LCOMMENT") and + ptok == "TK_OP" and sinfos[prev] == "-" then + -- keep token + else + settoken() -- remove entirely + end + else--is_realtoken + -- check a pair of grammar tokens, if can abut, then + -- delete space token entirely, otherwise keep one space + local s = checkpair(prev, i + 1) + if s == "" then + settoken() -- remove entirely + else + settoken("TK_SPACE", " ") + end + end + end + + end + end + + else + error("unidentified token encountered") + end + + i = i + 1 + end--while + repack_tokens() + + -- Processing loop (PASS 2) + if opt_eols then + i = 1 + -- Aggressive EOL removal only works with most non-grammar tokens + -- optimized away because it is a rather simple scheme -- basically + -- it just checks 'real' token pairs around EOLs. + if stoks[1] == "TK_COMMENT" then + -- first comment still existing must be shbang, skip whole line + i = 3 + end + while true do + tok = stoks[i] + + if tok == "TK_EOS" then -- end of stream/pass + break + + elseif tok == "TK_EOL" then -- consider each TK_EOL + local t1, t2 = stoks[i - 1], stoks[i + 1] + if is_realtoken[t1] and is_realtoken[t2] then -- sanity check + local s = checkpair(i - 1, i + 1) + if s == "" or t2 == "TK_EOS" then + settoken() -- remove entirely + end + end + end--if tok + + i = i + 1 + end--while + repack_tokens() + end + + if opt_details and opt_details > 0 then print() end -- spacing + return stoks, sinfos, stoklns +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/optparser.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/optparser.lua new file mode 100644 index 000000000..162b8818e --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/optparser.lua @@ -0,0 +1,644 @@ +--------- +-- This module does parser-based optimizations. +-- +-- **Notes:** +-- +-- * The processing load is quite significant, but since this is an +-- off-line text processor, I believe we can wait a few seconds. +-- * TODO: Might process "local a,a,a" wrongly... need tests! +-- * TODO: Remove position handling if overlapped locals (rem < 0) +-- needs more study, to check behaviour. +-- * TODO: There are probably better ways to do allocation, e.g. by +-- choosing better methods to sort and pick locals... +-- * TODO: We don't need 53*63 two-letter identifiers; we can make +-- do with significantly less depending on how many that are really +-- needed and improve entropy; e.g. 13 needed -> choose 4*4 instead. +---- +local byte = string.byte +local char = string.char +local concat = table.concat +local fmt = string.format +local pairs = pairs +local rep = string.rep +local sort = table.sort +local sub = string.sub + + +local M = {} + +-- Letter frequencies for reducing symbol entropy (fixed version) +-- * Might help a wee bit when the output file is compressed +-- * See Wikipedia: http://en.wikipedia.org/wiki/Letter_frequencies +-- * We use letter frequencies according to a Linotype keyboard, plus +-- the underscore, and both lower case and upper case letters. +-- * The arrangement below (LC, underscore, %d, UC) is arbitrary. +-- * This is certainly not optimal, but is quick-and-dirty and the +-- process has no significant overhead +local LETTERS = "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ" +local ALPHANUM = "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ" + +-- Names or identifiers that must be skipped. +-- (The first two lines are for keywords.) +local SKIP_NAME = {} +for v in ([[ +and break do else elseif end false for function if in +local nil not or repeat return then true until while +self _ENV]]):gmatch("%S+") do + SKIP_NAME[v] = true +end + + +local toklist, seminfolist, -- token lists (lexer output) + tokpar, seminfopar, xrefpar, -- token lists (parser output) + globalinfo, localinfo, -- variable information tables + statinfo, -- statment type table + globaluniq, localuniq, -- unique name tables + var_new, -- index of new variable names + varlist -- list of output variables + +--- Preprocesses information table to get lists of unique names. +-- +-- @tparam {table,...} infotable +-- @treturn table +local function preprocess(infotable) + local uniqtable = {} + for i = 1, #infotable do -- enumerate info table + local obj = infotable[i] + local name = obj.name + + if not uniqtable[name] then -- not found, start an entry + uniqtable[name] = { + decl = 0, token = 0, size = 0, + } + end + + local uniq = uniqtable[name] -- count declarations, tokens, size + uniq.decl = uniq.decl + 1 + local xref = obj.xref + local xcount = #xref + uniq.token = uniq.token + xcount + uniq.size = uniq.size + xcount * #name + + if obj.decl then -- if local table, create first,last pairs + obj.id = i + obj.xcount = xcount + if xcount > 1 then -- if ==1, means local never accessed + obj.first = xref[2] + obj.last = xref[xcount] + end + + else -- if global table, add a back ref + uniq.id = i + end + + end--for + return uniqtable +end + +--- Calculates actual symbol frequencies, in order to reduce entropy. +-- +-- * This may help further reduce the size of compressed sources. +-- * Note that since parsing optimizations is put before lexing +-- optimizations, the frequency table is not exact! +-- * Yes, this will miss --keep block comments too... +-- +-- @tparam table option +local function recalc_for_entropy(option) + -- table of token classes to accept in calculating symbol frequency + local ACCEPT = { + TK_KEYWORD = true, TK_NAME = true, TK_NUMBER = true, + TK_STRING = true, TK_LSTRING = true, + } + if not option["opt-comments"] then + ACCEPT.TK_COMMENT = true + ACCEPT.TK_LCOMMENT = true + end + + -- Create a new table and remove any original locals by filtering. + local filtered = {} + for i = 1, #toklist do + filtered[i] = seminfolist[i] + end + for i = 1, #localinfo do -- enumerate local info table + local obj = localinfo[i] + local xref = obj.xref + for j = 1, obj.xcount do + local p = xref[j] + filtered[p] = "" -- remove locals + end + end + + local freq = {} -- reset symbol frequency table + for i = 0, 255 do freq[i] = 0 end + for i = 1, #toklist do -- gather symbol frequency + local tok, info = toklist[i], filtered[i] + if ACCEPT[tok] then + for j = 1, #info do + local c = byte(info, j) + freq[c] = freq[c] + 1 + end + end--if + end--for + + -- Re-sorts symbols according to actual frequencies. + -- + -- @tparam string symbols + -- @treturn string + local function resort(symbols) + local symlist = {} + for i = 1, #symbols do -- prepare table to sort + local c = byte(symbols, i) + symlist[i] = { c = c, freq = freq[c], } + end + sort(symlist, function(v1, v2) -- sort selected symbols + return v1.freq > v2.freq + end) + local charlist = {} -- reconstitute the string + for i = 1, #symlist do + charlist[i] = char(symlist[i].c) + end + return concat(charlist) + end + + LETTERS = resort(LETTERS) -- change letter arrangement + ALPHANUM = resort(ALPHANUM) +end + +--- Returns a string containing a new local variable name to use, and +-- a flag indicating whether it collides with a global variable. +-- +-- Trapping keywords and other names like 'self' is done elsewhere. +-- +-- @treturn string A new local variable name. +-- @treturn bool Whether the name collides with a global variable. +local function new_var_name() + local var + local cletters, calphanum = #LETTERS, #ALPHANUM + local v = var_new + if v < cletters then -- single char + v = v + 1 + var = sub(LETTERS, v, v) + else -- longer names + local range, sz = cletters, 1 -- calculate # chars fit + repeat + v = v - range + range = range * calphanum + sz = sz + 1 + until range > v + local n = v % cletters -- left side cycles faster + v = (v - n) / cletters -- do first char first + n = n + 1 + var = sub(LETTERS, n, n) + while sz > 1 do + local m = v % calphanum + v = (v - m) / calphanum + m = m + 1 + var = var..sub(ALPHANUM, m, m) + sz = sz - 1 + end + end + var_new = var_new + 1 + return var, globaluniq[var] ~= nil +end + +--- Calculates and prints some statistics. +-- +-- Note: probably better in main source, put here for now. +-- +-- @tparam table globaluniq +-- @tparam table localuniq +-- @tparam table afteruniq +-- @tparam table option +local function stats_summary(globaluniq, localuniq, afteruniq, option) --luacheck: ignore 431 + local print = M.print or print + local opt_details = option.DETAILS + if option.QUIET then return end + + local uniq_g , uniq_li, uniq_lo = 0, 0, 0 + local decl_g, decl_li, decl_lo = 0, 0, 0 + local token_g, token_li, token_lo = 0, 0, 0 + local size_g, size_li, size_lo = 0, 0, 0 + + local function avg(c, l) -- safe average function + if c == 0 then return 0 end + return l / c + end + + -- Collect statistics (Note: globals do not have declarations!) + for _, uniq in pairs(globaluniq) do + uniq_g = uniq_g + 1 + token_g = token_g + uniq.token + size_g = size_g + uniq.size + end + for _, uniq in pairs(localuniq) do + uniq_li = uniq_li + 1 + decl_li = decl_li + uniq.decl + token_li = token_li + uniq.token + size_li = size_li + uniq.size + end + for _, uniq in pairs(afteruniq) do + uniq_lo = uniq_lo + 1 + decl_lo = decl_lo + uniq.decl + token_lo = token_lo + uniq.token + size_lo = size_lo + uniq.size + end + local uniq_ti = uniq_g + uniq_li + local decl_ti = decl_g + decl_li + local token_ti = token_g + token_li + local size_ti = size_g + size_li + local uniq_to = uniq_g + uniq_lo + local decl_to = decl_g + decl_lo + local token_to = token_g + token_lo + local size_to = size_g + size_lo + + -- Detailed stats: global list + if opt_details then + local sorted = {} -- sort table of unique global names by size + for name, uniq in pairs(globaluniq) do + uniq.name = name + sorted[#sorted + 1] = uniq + end + sort(sorted, function(v1, v2) + return v1.size > v2.size + end) + + do + local tabf1, tabf2 = "%8s%8s%10s %s", "%8d%8d%10.2f %s" + local hl = rep("-", 44) + print("*** global variable list (sorted by size) ***\n"..hl) + print(fmt(tabf1, "Token", "Input", "Input", "Global")) + print(fmt(tabf1, "Count", "Bytes", "Average", "Name")) + print(hl) + for i = 1, #sorted do + local uniq = sorted[i] + print(fmt(tabf2, uniq.token, uniq.size, avg(uniq.token, uniq.size), uniq.name)) + end + print(hl) + print(fmt(tabf2, token_g, size_g, avg(token_g, size_g), "TOTAL")) + print(hl.."\n") + end + + -- Detailed stats: local list + do + local tabf1, tabf2 = "%8s%8s%8s%10s%8s%10s %s", "%8d%8d%8d%10.2f%8d%10.2f %s" + local hl = rep("-", 70) + print("*** local variable list (sorted by allocation order) ***\n"..hl) + print(fmt(tabf1, "Decl.", "Token", "Input", "Input", "Output", "Output", "Global")) + print(fmt(tabf1, "Count", "Count", "Bytes", "Average", "Bytes", "Average", "Name")) + print(hl) + for i = 1, #varlist do -- iterate according to order assigned + local name = varlist[i] + local uniq = afteruniq[name] + local old_t, old_s = 0, 0 + for j = 1, #localinfo do -- find corresponding old names and calculate + local obj = localinfo[j] + if obj.name == name then + old_t = old_t + obj.xcount + old_s = old_s + obj.xcount * #obj.oldname + end + end + print(fmt(tabf2, uniq.decl, uniq.token, old_s, avg(old_t, old_s), + uniq.size, avg(uniq.token, uniq.size), name)) + end + print(hl) + print(fmt(tabf2, decl_lo, token_lo, size_li, avg(token_li, size_li), + size_lo, avg(token_lo, size_lo), "TOTAL")) + print(hl.."\n") + end + end--if opt_details + + -- Display output + do + local tabf1, tabf2 = "%-16s%8s%8s%8s%8s%10s", "%-16s%8d%8d%8d%8d%10.2f" + local hl = rep("-", 58) + print("*** local variable optimization summary ***\n"..hl) + print(fmt(tabf1, "Variable", "Unique", "Decl.", "Token", "Size", "Average")) + print(fmt(tabf1, "Types", "Names", "Count", "Count", "Bytes", "Bytes")) + print(hl) + print(fmt(tabf2, "Global", uniq_g, decl_g, token_g, size_g, avg(token_g, size_g))) + print(hl) + print(fmt(tabf2, "Local (in)", uniq_li, decl_li, token_li, size_li, avg(token_li, size_li))) + print(fmt(tabf2, "TOTAL (in)", uniq_ti, decl_ti, token_ti, size_ti, avg(token_ti, size_ti))) + print(hl) + print(fmt(tabf2, "Local (out)", uniq_lo, decl_lo, token_lo, size_lo, avg(token_lo, size_lo))) + print(fmt(tabf2, "TOTAL (out)", uniq_to, decl_to, token_to, size_to, avg(token_to, size_to))) + print(hl.."\n") + end +end + +--- Does experimental optimization for f("string") statements. +-- +-- It's safe to delete parentheses without adding whitespace, as both +-- kinds of strings can abut with anything else. +local function optimize_func1() + + local function is_strcall(j) -- find f("string") pattern + local t1 = tokpar[j + 1] or "" + local t2 = tokpar[j + 2] or "" + local t3 = tokpar[j + 3] or "" + if t1 == "(" and t2 == "" and t3 == ")" then + return true + end + end + + local del_list = {} -- scan for function pattern, + local i = 1 -- tokens to be deleted are marked + while i <= #tokpar do + local id = statinfo[i] + if id == "call" and is_strcall(i) then -- found & mark () + del_list[i + 1] = true -- '(' + del_list[i + 3] = true -- ')' + i = i + 3 + end + i = i + 1 + end + + -- Delete a token and adjust all relevant tables. + -- * Currently invalidates globalinfo and localinfo (not updated), + -- so any other optimization is done after processing locals + -- (of course, we can also lex the source data again...). + -- * Faster one-pass token deletion. + local del_list2 = {} + do + local i, dst, idend = 1, 1, #tokpar + while dst <= idend do -- process parser tables + if del_list[i] then -- found a token to delete? + del_list2[xrefpar[i]] = true + i = i + 1 + end + if i > dst then + if i <= idend then -- shift table items lower + tokpar[dst] = tokpar[i] + seminfopar[dst] = seminfopar[i] + xrefpar[dst] = xrefpar[i] - (i - dst) + statinfo[dst] = statinfo[i] + else -- nil out excess entries + tokpar[dst] = nil + seminfopar[dst] = nil + xrefpar[dst] = nil + statinfo[dst] = nil + end + end + i = i + 1 + dst = dst + 1 + end + end + + do + local i, dst, idend = 1, 1, #toklist + while dst <= idend do -- process lexer tables + if del_list2[i] then -- found a token to delete? + i = i + 1 + end + if i > dst then + if i <= idend then -- shift table items lower + toklist[dst] = toklist[i] + seminfolist[dst] = seminfolist[i] + else -- nil out excess entries + toklist[dst] = nil + seminfolist[dst] = nil + end + end + i = i + 1 + dst = dst + 1 + end + end +end + +--- Does local variable optimization. +-- +-- @tparam {[string]=bool,...} option +local function optimize_locals(option) + var_new = 0 -- reset variable name allocator + varlist = {} + + -- Preprocess global/local tables, handle entropy reduction. + globaluniq = preprocess(globalinfo) + localuniq = preprocess(localinfo) + if option["opt-entropy"] then -- for entropy improvement + recalc_for_entropy(option) + end + + -- Build initial declared object table, then sort according to + -- token count, this might help assign more tokens to more common + -- variable names such as 'e' thus possibly reducing entropy. + -- * An object knows its localinfo index via its 'id' field. + -- * Special handling for "self" and "_ENV" special local (parameter) here. + local object = {} + for i = 1, #localinfo do + object[i] = localinfo[i] + end + sort(object, function(v1, v2) -- sort largest first + return v1.xcount > v2.xcount + end) + + -- The special "self" and "_ENV" function parameters must be preserved. + -- * The allocator below will never use "self", so it is safe to + -- keep those implicit declarations as-is. + local temp, j, used_specials = {}, 1, {} + for i = 1, #object do + local obj = object[i] + if not obj.is_special then + temp[j] = obj + j = j + 1 + else + used_specials[#used_specials + 1] = obj.name + end + end + object = temp + + -- A simple first-come first-served heuristic name allocator, + -- note that this is in no way optimal... + -- * Each object is a local variable declaration plus existence. + -- * The aim is to assign short names to as many tokens as possible, + -- so the following tries to maximize name reuse. + -- * Note that we preserve sort order. + local nobject = #object + while nobject > 0 do + local varname, gcollide + repeat + varname, gcollide = new_var_name() -- collect a variable name + until not SKIP_NAME[varname] -- skip all special names + varlist[#varlist + 1] = varname -- keep a list + local oleft = nobject + + -- If variable name collides with an existing global, the name + -- cannot be used by a local when the name is accessed as a global + -- during which the local is alive (between 'act' to 'rem'), so + -- we drop objects that collides with the corresponding global. + if gcollide then + -- find the xref table of the global + local gref = globalinfo[globaluniq[varname].id].xref + local ngref = #gref + -- enumerate for all current objects; all are valid at this point + for i = 1, nobject do + local obj = object[i] + local act, rem = obj.act, obj.rem -- 'live' range of local + -- if rem < 0, it is a -id to a local that had the same name + -- so follow rem to extend it; does this make sense? + while rem < 0 do + rem = localinfo[-rem].rem + end + local drop + for j = 1, ngref do + local p = gref[j] + if p >= act and p <= rem then drop = true end -- in range? + end + if drop then + obj.skip = true + oleft = oleft - 1 + end + end--for + end--if gcollide + + -- Now the first unassigned local (since it's sorted) will be the + -- one with the most tokens to rename, so we set this one and then + -- eliminate all others that collides, then any locals that left + -- can then reuse the same variable name; this is repeated until + -- all local declaration that can use this name is assigned. + -- + -- The criteria for local-local reuse/collision is: + -- A is the local with a name already assigned + -- B is the unassigned local under consideration + -- => anytime A is accessed, it cannot be when B is 'live' + -- => to speed up things, we have first/last accesses noted + while oleft > 0 do + local i = 1 + while object[i].skip do -- scan for first object + i = i + 1 + end + + -- First object is free for assignment of the variable name + -- [first,last] gives the access range for collision checking. + oleft = oleft - 1 + local obja = object[i] + i = i + 1 + obja.newname = varname + obja.skip = true + obja.done = true + local first, last = obja.first, obja.last + local xref = obja.xref + + -- Then, scan all the rest and drop those colliding. + -- If A was never accessed then it'll never collide with anything + -- otherwise trivial skip if: + -- * B was activated after A's last access (last < act), + -- * B was removed before A's first access (first > rem), + -- if not, see detailed skip below... + if first and oleft > 0 then -- must have at least 1 access + local scanleft = oleft + while scanleft > 0 do + while object[i].skip do -- next valid object + i = i + 1 + end + scanleft = scanleft - 1 + local objb = object[i] + i = i + 1 + local act, rem = objb.act, objb.rem -- live range of B + -- if rem < 0, extend range of rem thru' following local + while rem < 0 do + rem = localinfo[-rem].rem + end + + if not(last < act or first > rem) then -- possible collision + + -- B is activated later than A or at the same statement, + -- this means for no collision, A cannot be accessed when B + -- is alive, since B overrides A (or is a peer). + if act >= obja.act then + for j = 1, obja.xcount do -- ... then check every access + local p = xref[j] + if p >= act and p <= rem then -- A accessed when B live! + oleft = oleft - 1 + objb.skip = true + break + end + end--for + + -- A is activated later than B, this means for no collision, + -- A's access is okay since it overrides B, but B's last + -- access need to be earlier than A's activation time. + else + if objb.last and objb.last >= obja.act then + oleft = oleft - 1 + objb.skip = true + end + end + end + + if oleft == 0 then break end + end + end--if first + + end--while + + -- After assigning all possible locals to one variable name, the + -- unassigned locals/objects have the skip field reset and the table + -- is compacted, to hopefully reduce iteration time. + local temp, j = {}, 1 + for i = 1, nobject do + local obj = object[i] + if not obj.done then + obj.skip = false + temp[j] = obj + j = j + 1 + end + end + object = temp -- new compacted object table + nobject = #object -- objects left to process + + end--while + + -- After assigning all locals with new variable names, we can + -- patch in the new names, and reprocess to get 'after' stats. + for i = 1, #localinfo do -- enumerate all locals + local obj = localinfo[i] + local xref = obj.xref + if obj.newname then -- if got new name, patch it in + for j = 1, obj.xcount do + local p = xref[j] -- xrefs indexes the token list + seminfolist[p] = obj.newname + end + obj.name, obj.oldname -- adjust names + = obj.newname, obj.name + else + obj.oldname = obj.name -- for cases like 'self' + end + end + + -- Deal with statistics output. + for _, name in ipairs(used_specials) do + varlist[#varlist + 1] = name + end + local afteruniq = preprocess(localinfo) + stats_summary(globaluniq, localuniq, afteruniq, option) +end + +--- The main entry point. +-- +-- @tparam table option +-- @tparam {string,...} _toklist +-- @tparam {string,...} _seminfolist +-- @tparam table xinfo +function M.optimize(option, _toklist, _seminfolist, xinfo) + -- set tables + toklist, seminfolist -- from lexer + = _toklist, _seminfolist + tokpar, seminfopar, xrefpar -- from parser + = xinfo.toklist, xinfo.seminfolist, xinfo.xreflist + globalinfo, localinfo, statinfo -- from parser + = xinfo.globalinfo, xinfo.localinfo, xinfo.statinfo + + -- Optimize locals. + if option["opt-locals"] then + optimize_locals(option) + end + + -- Other optimizations. + if option["opt-experimental"] then -- experimental + optimize_func1() + -- WARNING globalinfo and localinfo now invalidated! + end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/example.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/example.lua new file mode 100644 index 000000000..6a04e33a4 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/example.lua @@ -0,0 +1,90 @@ +--------- +-- Example of a plugin for LuaSrcDiet. +-- +-- WARNING: highly experimental! interface liable to change +-- +-- **Notes:** +-- +-- * Any function can be omitted and LuaSrcDiet won't call it. +-- * The functions are: +-- (1) init(_option, _srcfl, _destfl) +-- (2) post_load(z) can return z +-- (3) post_lex(toklist, seminfolist, toklnlist) +-- (4) post_parse(globalinfo, localinfo) +-- (5) post_optparse() +-- (6) post_optlex(toklist, seminfolist, toklnlist) +-- * Older tables can be copied and kept in the plugin and used later. +-- * If you modify 'option', remember that LuaSrcDiet might be +-- processing more than one file. +-- * Arrangement of the functions is not final! +-- * TODO: can't process additional options from command line yet +---- + +local M = {} + +local option -- local reference to list of options +local srcfl, destfl -- filenames +local old_quiet + +local function print(...) -- handle quiet option + if option.QUIET then return end + _G.print(...) +end + +--- Initialization. +-- +-- @tparam {[string]=bool,...} _option +-- @tparam string _srcfl Path of the source file. +-- @tparam string _destfl Path of the destination file. +function M.init(_option, _srcfl, _destfl) + option = _option + srcfl, destfl = _srcfl, _destfl + -- plugin can impose its own option starting from here +end + +--- Message display, post-load processing, can return z. +function M.post_load(z) + -- this message will print after the LuaSrcDiet title message + print([[ +Example plugin module for LuaSrcDiet +]]) + print("Example: source file name is '"..srcfl.."'") + print("Example: destination file name is '"..destfl.."'") + print("Example: the size of the source file is "..#z.." bytes") + -- returning z is optional; this allows optional replacement of + -- the source data prior to lexing + return z +end + +--- Post-lexing processing, can work on lexer table output. +function M.post_lex(toklist, seminfolist, toklnlist) --luacheck: ignore + print("Example: the number of lexed elements is "..#toklist) +end + +--- Post-parsing processing, gives globalinfo, localinfo. +function M.post_parse(globalinfo, localinfo) + print("Example: size of globalinfo is "..#globalinfo) + print("Example: size of localinfo is "..#localinfo) + old_quiet = option.QUIET + option.QUIET = true +end + +--- Post-parser optimization processing, can get tables from elsewhere. +function M.post_optparse() + option.QUIET = old_quiet + print("Example: pretend to do post-optparse") +end + +--- Post-lexer optimization processing, can get tables from elsewhere. +function M.post_optlex(toklist, seminfolist, toklnlist) --luacheck: ignore + print("Example: pretend to do post-optlex") + -- restore old settings, other file might need original settings + option.QUIET = old_quiet + -- option.EXIT can be set at the end of any post_* function to stop + -- further processing and exit for the current file being worked on + -- in this case, final stats printout is disabled and the output will + -- not be written to the destination file + option.EXIT = true +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/html.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/html.lua new file mode 100644 index 000000000..2305ee710 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/html.lua @@ -0,0 +1,177 @@ +--------- +-- Turns Lua 5.1 source code into HTML files. +-- +-- WARNING: highly experimental! interface liable to change +-- +-- **Notes:** +-- +-- * This HTML highlighter marks globals brightly so that their usage +-- can be manually optimized. +-- * Either uses a .html extension for output files or it follows the +-- -o option. +-- * The HTML style tries to follow that of the Lua wiki. +---- +local fs = require "luasrcdiet.fs" + +local concat = table.concat +local find = string.find +local fmt = string.format +local sub = string.sub + +local M = {} + +local HTML_EXT = ".html" +local ENTITIES = { + ["&"] = "&", ["<"] = "<", [">"] = ">", + ["'"] = "'", ["\""] = """, +} + +-- simple headers and footers +local HEADER = [[ + + + +%s + + + + +
+]]
+local FOOTER = [[
+
+ + +]] +-- for more, please see wikimain.css from the Lua wiki site +local STYLESHEET = [[ +BODY { + background: white; + color: navy; +} +pre.code { color: black; } +span.comment { color: #00a000; } +span.string { color: #009090; } +span.keyword { color: black; font-weight: bold; } +span.number { color: #993399; } +span.operator { } +span.name { } +span.global { color: #ff0000; font-weight: bold; } +span.local { color: #0000ff; font-weight: bold; } +]] + +local option -- local reference to list of options +local srcfl, destfl -- filenames +local toklist, seminfolist -- token data + +local function print(...) -- handle quiet option + if option.QUIET then return end + _G.print(...) +end + +--- Initialization. +function M.init(_option, _srcfl) + option = _option + srcfl = _srcfl + local extb, _ = find(srcfl, "%.[^%.%\\%/]*$") + local basename = srcfl + if extb and extb > 1 then + basename = sub(srcfl, 1, extb - 1) + end + destfl = basename..HTML_EXT + if option.OUTPUT_FILE then + destfl = option.OUTPUT_FILE + end + if srcfl == destfl then + error("output filename identical to input filename") + end +end + +--- Message display, post-load processing. +function M.post_load() + print([[ +HTML plugin module for LuaSrcDiet +]]) + print("Exporting: "..srcfl.." -> "..destfl.."\n") +end + +--- Post-lexing processing, can work on lexer table output. +function M.post_lex(_toklist, _seminfolist) + toklist, seminfolist = _toklist, _seminfolist +end + +--- Escapes the usual suspects for HTML/XML. +local function do_entities(z) + local i = 1 + while i <= #z do + local c = sub(z, i, i) + local d = ENTITIES[c] + if d then + c = d + z = sub(z, 1, i - 1)..c..sub(z, i + 1) + end + i = i + #c + end--while + return z +end + +--- Post-parsing processing, gives globalinfo, localinfo. +function M.post_parse(globalinfo, localinfo) + local html = {} + local function add(s) -- html helpers + html[#html + 1] = s + end + local function span(class, s) + add(''..s..'') + end + + for i = 1, #globalinfo do -- mark global identifiers as TK_GLOBAL + local obj = globalinfo[i] + local xref = obj.xref + for j = 1, #xref do + local p = xref[j] + toklist[p] = "TK_GLOBAL" + end + end--for + + for i = 1, #localinfo do -- mark local identifiers as TK_LOCAL + local obj = localinfo[i] + local xref = obj.xref + for j = 1, #xref do + local p = xref[j] + toklist[p] = "TK_LOCAL" + end + end--for + + add(fmt(HEADER, -- header and leading stuff + do_entities(srcfl), + STYLESHEET)) + for i = 1, #toklist do -- enumerate token list + local tok, info = toklist[i], seminfolist[i] + if tok == "TK_KEYWORD" then + span("keyword", info) + elseif tok == "TK_STRING" or tok == "TK_LSTRING" then + span("string", do_entities(info)) + elseif tok == "TK_COMMENT" or tok == "TK_LCOMMENT" then + span("comment", do_entities(info)) + elseif tok == "TK_GLOBAL" then + span("global", info) + elseif tok == "TK_LOCAL" then + span("local", info) + elseif tok == "TK_NAME" then + span("name", info) + elseif tok == "TK_NUMBER" then + span("number", info) + elseif tok == "TK_OP" then + span("operator", do_entities(info)) + elseif tok ~= "TK_EOS" then -- TK_EOL, TK_SPACE + add(info) + end + end--for + add(FOOTER) + assert(fs.write_file(destfl, concat(html), "wb")) + option.EXIT = true +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/sloc.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/sloc.lua new file mode 100644 index 000000000..50b811f8f --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/sloc.lua @@ -0,0 +1,89 @@ +--------- +-- Calculates SLOC for Lua 5.1 scripts +-- +-- WARNING: highly experimental! interface liable to change +-- +-- **Notes:** +-- +-- * SLOC's behaviour is based on David Wheeler's SLOCCount. +-- * Empty lines and comment don't count as significant. +-- * Empty lines in long strings are also insignificant. This is +-- debatable. In SLOCCount, this allows counting of invalid multi- +-- line strings for C. But an empty line is still an empty line. +-- * Ignores the --quiet option, print own result line. +---- + +local M = {} + +local option -- local reference to list of options +local srcfl -- source file name + +function M.init(_option, _srcfl) + option = _option + option.QUIET = true + srcfl = _srcfl +end + +--- Splits a block into a table of lines (minus EOLs). +-- +-- @tparam string blk +-- @treturn {string,...} lines +local function split(blk) + local lines = {} + local i, nblk = 1, #blk + while i <= nblk do + local p, q, r, s = blk:find("([\r\n])([\r\n]?)", i) + if not p then + p = nblk + 1 + end + lines[#lines + 1] = blk:sub(i, p - 1) + i = p + 1 + if p < nblk and q > p and r ~= s then -- handle Lua-style CRLF, LFCR + i = i + 1 + end + end + return lines +end + +--- Post-lexing processing, can work on lexer table output. +function M.post_lex(toklist, seminfolist, toklnlist) + local lnow, sloc = 0, 0 + local function chk(ln) -- if a new line, count it as an SLOC + if ln > lnow then -- new line # must be > old line # + sloc = sloc + 1; lnow = ln + end + end + for i = 1, #toklist do -- enumerate over all tokens + local tok, info, ln + = toklist[i], seminfolist[i], toklnlist[i] + + if tok == "TK_KEYWORD" or tok == "TK_NAME" or -- significant + tok == "TK_NUMBER" or tok == "TK_OP" then + chk(ln) + + -- Both TK_STRING and TK_LSTRING may be multi-line, hence, a loop + -- is needed in order to mark off lines one-by-one. Since llex.lua + -- currently returns the line number of the last part of the string, + -- we must subtract in order to get the starting line number. + elseif tok == "TK_STRING" then -- possible multi-line + local t = split(info) + ln = ln - #t + 1 + for _ = 1, #t do + chk(ln); ln = ln + 1 + end + + elseif tok == "TK_LSTRING" then -- possible multi-line + local t = split(info) + ln = ln - #t + 1 + for j = 1, #t do + if t[j] ~= "" then chk(ln) end + ln = ln + 1 + end + -- Other tokens are comments or whitespace and are ignored. + end + end--for + print(srcfl..": "..sloc) -- display result + option.EXIT = true +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/luasrcdiet/utils.lua b/Utils/luarocks/share/lua/5.1/luasrcdiet/utils.lua new file mode 100644 index 000000000..611e9de8b --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/luasrcdiet/utils.lua @@ -0,0 +1,30 @@ +--------- +-- General utility functions. +-- +-- **Note: This module is not part of public API!** +---- +local ipairs = ipairs +local pairs = pairs + +local M = {} + +--- Returns a new table containing the contents of all the given tables. +-- Tables are iterated using @{pairs}, so this function is intended for tables +-- that represent *associative arrays*. Entries with duplicate keys are +-- overwritten with the values from a later table. +-- +-- @tparam {table,...} ... The tables to merge. +-- @treturn table A new table. +function M.merge (...) + local result = {} + + for _, tab in ipairs{...} do + for key, val in pairs(tab) do + result[key] = val + end + end + + return result +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/markdown.lua b/Utils/luarocks/share/lua/5.1/markdown.lua new file mode 100644 index 000000000..b16d43b3d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/markdown.lua @@ -0,0 +1,1359 @@ +#!/usr/bin/env lua + +--[[ +# markdown.lua -- version 0.32 + + + +**Author:** Niklas Frykholm, +**Date:** 31 May 2008 + +This is an implementation of the popular text markup language Markdown in pure Lua. +Markdown can convert documents written in a simple and easy to read text format +to well-formatted HTML. For a more thourough description of Markdown and the Markdown +syntax, see . + +The original Markdown source is written in Perl and makes heavy use of advanced +regular expression techniques (such as negative look-ahead, etc) which are not available +in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground +up. It is probably not completely bug free. If you notice any bugs, please report them to +me. A unit test that exposes the error is helpful. + +## Usage + + require "markdown" + markdown(source) + +``markdown.lua`` exposes a single global function named ``markdown(s)`` which applies the +Markdown transformation to the specified string. + +``markdown.lua`` can also be used directly from the command line: + + lua markdown.lua test.md + +Creates a file ``test.html`` with the converted content of ``test.md``. Run: + + lua markdown.lua -h + +For a description of the command-line options. + +``markdown.lua`` uses the same license as Lua, the MIT license. + +## License + +Copyright © 2008 Niklas Frykholm. + +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: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +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. + +## Version history + +- **0.32** -- 31 May 2008 + - Fix for links containing brackets +- **0.31** -- 1 Mar 2008 + - Fix for link definitions followed by spaces +- **0.30** -- 25 Feb 2008 + - Consistent behavior with Markdown when the same link reference is reused +- **0.29** -- 24 Feb 2008 + - Fix for
 blocks with spaces in them
+-	**0.28** -- 18 Feb 2008
+	-	Fix for link encoding
+-	**0.27** -- 14 Feb 2008
+	-	Fix for link database links with ()
+-	**0.26** -- 06 Feb 2008
+	-	Fix for nested italic and bold markers
+-	**0.25** -- 24 Jan 2008
+	-	Fix for encoding of naked <
+-	**0.24** -- 21 Jan 2008
+	-	Fix for link behavior.
+-	**0.23** -- 10 Jan 2008
+	-	Fix for a regression bug in longer expressions in italic or bold.
+-	**0.22** -- 27 Dec 2007
+	-	Fix for crash when processing blocks with a percent sign in them.
+-	**0.21** -- 27 Dec 2007
+	- 	Fix for combined strong and emphasis tags
+-	**0.20** -- 13 Oct 2007
+	-	Fix for < as well in image titles, now matches Dingus behavior
+-	**0.19** -- 28 Sep 2007
+	-	Fix for quotation marks " and ampersands & in link and image titles.
+-	**0.18** -- 28 Jul 2007
+	-	Does not crash on unmatched tags (behaves like standard markdown)
+-	**0.17** -- 12 Apr 2007
+	-	Fix for links with %20 in them.
+-	**0.16** -- 12 Apr 2007
+	-	Do not require arg global to exist.
+-	**0.15** -- 28 Aug 2006
+	-	Better handling of links with underscores in them.
+-	**0.14** -- 22 Aug 2006
+	-	Bug for *`foo()`*
+-	**0.13** -- 12 Aug 2006
+	-	Added -l option for including stylesheet inline in document.
+	-	Fixed bug in -s flag.
+	-	Fixed emphasis bug.
+-	**0.12** -- 15 May 2006
+	-	Fixed several bugs to comply with MarkdownTest 1.0 
+-	**0.11** -- 12 May 2006
+	-	Fixed bug for escaping `*` and `_` inside code spans.
+	-	Added license terms.
+	-	Changed join() to table.concat().
+-	**0.10** -- 3 May 2006
+	-	Initial public release.
+
+// Niklas
+]]
+
+
+-- Set up a table for holding local functions to avoid polluting the global namespace
+local M = {}
+local MT = {__index = _G}
+setmetatable(M, MT)
+setfenv(1, M)
+
+----------------------------------------------------------------------
+-- Utility functions
+----------------------------------------------------------------------
+
+-- Locks table t from changes, writes an error if someone attempts to change the table.
+-- This is useful for detecting variables that have "accidently" been made global. Something
+-- I tend to do all too much.
+function lock(t)
+	function lock_new_index(t, k, v)
+		error("module has been locked -- " .. k .. " must be declared local", 2)
+	end
+
+	local mt = {__newindex = lock_new_index}
+	if getmetatable(t) then mt.__index = getmetatable(t).__index end
+	setmetatable(t, mt)
+end
+
+-- Returns the result of mapping the values in table t through the function f
+function map(t, f)
+	local out = {}
+	for k,v in pairs(t) do out[k] = f(v,k) end
+	return out
+end
+
+-- The identity function, useful as a placeholder.
+function identity(text) return text end
+
+-- Functional style if statement. (NOTE: no short circuit evaluation)
+function iff(t, a, b) if t then return a else return b end end
+
+-- Splits the text into an array of separate lines.
+function split(text, sep)
+	sep = sep or "\n"
+	local lines = {}
+	local pos = 1
+	while true do
+		local b,e = text:find(sep, pos)
+		if not b then table.insert(lines, text:sub(pos)) break end
+		table.insert(lines, text:sub(pos, b-1))
+		pos = e + 1
+	end
+	return lines
+end
+
+-- Converts tabs to spaces
+function detab(text)
+	local tab_width = 4
+	local function rep(match)
+		local spaces = -match:len()
+		while spaces<1 do spaces = spaces + tab_width end
+		return match .. string.rep(" ", spaces)
+	end
+	text = text:gsub("([^\n]-)\t", rep)
+	return text
+end
+
+-- Applies string.find for every pattern in the list and returns the first match
+function find_first(s, patterns, index)
+	local res = {}
+	for _,p in ipairs(patterns) do
+		local match = {s:find(p, index)}
+		if #match>0 and (#res==0 or match[1] < res[1]) then res = match end
+	end
+	return unpack(res)
+end
+
+-- If a replacement array is specified, the range [start, stop] in the array is replaced
+-- with the replacement array and the resulting array is returned. Without a replacement
+-- array the section of the array between start and stop is returned.
+function splice(array, start, stop, replacement)
+	if replacement then
+		local n = stop - start + 1
+		while n > 0 do
+			table.remove(array, start)
+			n = n - 1
+		end
+		for i,v in ipairs(replacement) do
+			table.insert(array, start, v)
+		end
+		return array
+	else
+		local res = {}
+		for i = start,stop do
+			table.insert(res, array[i])
+		end
+		return res
+	end
+end
+
+-- Outdents the text one step.
+function outdent(text)
+	text = "\n" .. text
+	text = text:gsub("\n  ? ? ?", "\n")
+	text = text:sub(2)
+	return text
+end
+
+-- Indents the text one step.
+function indent(text)
+	text = text:gsub("\n", "\n    ")
+	return text
+end
+
+-- Does a simple tokenization of html data. Returns the data as a list of tokens. 
+-- Each token is a table with a type field (which is either "tag" or "text") and
+-- a text field (which contains the original token data).
+function tokenize_html(html)
+	local tokens = {}
+	local pos = 1
+	while true do
+		local start = find_first(html, {"", start)
+		elseif html:match("^<%?", start) then
+			_,stop = html:find("?>", start)
+		else
+			_,stop = html:find("%b<>", start)
+		end
+		if not stop then
+			-- error("Could not match html tag " .. html:sub(start,start+30)) 
+		 	table.insert(tokens, {type="text", text=html:sub(start, start)})
+			pos = start + 1
+		else
+			table.insert(tokens, {type="tag", text=html:sub(start, stop)})
+			pos = stop + 1
+		end
+	end
+	return tokens
+end
+
+----------------------------------------------------------------------
+-- Hash
+----------------------------------------------------------------------
+
+-- This is used to "hash" data into alphanumeric strings that are unique
+-- in the document. (Note that this is not cryptographic hash, the hash
+-- function is not one-way.) The hash procedure is used to protect parts
+-- of the document from further processing.
+
+local HASH = {
+	-- Has the hash been inited.
+	inited = false,
+	
+	-- The unique string prepended to all hash values. This is to ensure
+	-- that hash values do not accidently coincide with an actual existing
+	-- string in the document.
+	identifier = "",
+	
+	-- Counter that counts up for each new hash instance.
+	counter = 0,
+	
+	-- Hash table.
+	table = {}
+}
+
+-- Inits hashing. Creates a hash_identifier that doesn't occur anywhere
+-- in the text.
+function init_hash(text)
+	HASH.inited = true
+	HASH.identifier = ""
+	HASH.counter = 0
+	HASH.table = {}
+	
+	local s = "HASH"
+	local counter = 0
+	local id
+	while true do
+		id  = s .. counter
+		if not text:find(id, 1, true) then break end
+		counter = counter + 1
+	end
+	HASH.identifier = id
+end
+
+-- Returns the hashed value for s.
+function hash(s)
+	assert(HASH.inited)
+	if not HASH.table[s] then
+		HASH.counter = HASH.counter + 1
+		local id = HASH.identifier .. HASH.counter .. "X"
+		HASH.table[s] = id
+	end
+	return HASH.table[s]
+end
+
+----------------------------------------------------------------------
+-- Protection
+----------------------------------------------------------------------
+
+-- The protection module is used to "protect" parts of a document
+-- so that they are not modified by subsequent processing steps. 
+-- Protected parts are saved in a table for later unprotection
+
+-- Protection data
+local PD = {
+	-- Saved blocks that have been converted
+	blocks = {},
+
+	-- Block level tags that will be protected
+	tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote",
+	"pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset",
+	"iframe", "math", "ins", "del"}
+}
+
+-- Pattern for matching a block tag that begins and ends in the leftmost
+-- column and may contain indented subtags, i.e.
+-- 
+-- A nested block. +--
+-- Nested data. +--
+--
+function block_pattern(tag) + return "\n<" .. tag .. ".-\n[ \t]*\n" +end + +-- Pattern for matching a block tag that begins and ends with a newline +function line_pattern(tag) + return "\n<" .. tag .. ".-[ \t]*\n" +end + +-- Protects the range of characters from start to stop in the text and +-- returns the protected string. +function protect_range(text, start, stop) + local s = text:sub(start, stop) + local h = hash(s) + PD.blocks[h] = s + text = text:sub(1,start) .. h .. text:sub(stop) + return text +end + +-- Protect every part of the text that matches any of the patterns. The first +-- matching pattern is protected first, etc. +function protect_matches(text, patterns) + while true do + local start, stop = find_first(text, patterns) + if not start then break end + text = protect_range(text, start, stop) + end + return text +end + +-- Protects blocklevel tags in the specified text +function protect(text) + -- First protect potentially nested block tags + text = protect_matches(text, map(PD.tags, block_pattern)) + -- Then protect block tags at the line level. + text = protect_matches(text, map(PD.tags, line_pattern)) + -- Protect
and comment tags + text = protect_matches(text, {"\n]->[ \t]*\n"}) + text = protect_matches(text, {"\n[ \t]*\n"}) + return text +end + +-- Returns true if the string s is a hash resulting from protection +function is_protected(s) + return PD.blocks[s] +end + +-- Unprotects the specified text by expanding all the nonces +function unprotect(text) + for k,v in pairs(PD.blocks) do + v = v:gsub("%%", "%%%%") + text = text:gsub(k, v) + end + return text +end + + +---------------------------------------------------------------------- +-- Block transform +---------------------------------------------------------------------- + +-- The block transform functions transform the text on the block level. +-- They work with the text as an array of lines rather than as individual +-- characters. + +-- Returns true if the line is a ruler of (char) characters. +-- The line must contain at least three char characters and contain only spaces and +-- char characters. +function is_ruler_of(line, char) + if not line:match("^[ %" .. char .. "]*$") then return false end + if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end + return true +end + +-- Identifies the block level formatting present in the line +function classify(line) + local info = {line = line, text = line} + + if line:match("^ ") then + info.type = "indented" + info.outdented = line:sub(5) + return info + end + + for _,c in ipairs({'*', '-', '_', '='}) do + if is_ruler_of(line, c) then + info.type = "ruler" + info.ruler_char = c + return info + end + end + + if line == "" then + info.type = "blank" + return info + end + + if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then + local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") + info.type = "header" + info.level = m1:len() + info.text = m2 + return info + end + + if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then + local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") + info.type = "list_item" + info.list_type = "numeric" + info.number = 0 + number + info.text = text + return info + end + + if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then + local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") + info.type = "list_item" + info.list_type = "bullet" + info.bullet = bullet + info.text= text + return info + end + + if line:match("^>[ \t]?(.*)") then + info.type = "blockquote" + info.text = line:match("^>[ \t]?(.*)") + return info + end + + if is_protected(line) then + info.type = "raw" + info.html = unprotect(line) + return info + end + + info.type = "normal" + return info +end + +-- Find headers constisting of a normal line followed by a ruler and converts them to +-- header entries. +function headers(array) + local i = 1 + while i <= #array - 1 do + if array[i].type == "normal" and array[i+1].type == "ruler" and + (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then + local info = {line = array[i].line} + info.text = info.line + info.type = "header" + info.level = iff(array[i+1].ruler_char == "=", 1, 2) + table.remove(array, i+1) + array[i] = info + end + i = i + 1 + end + return array +end + +-- Find list blocks and convert them to protected data blocks +function lists(array, sublist) + local function process_list(arr) + local function any_blanks(arr) + for i = 1, #arr do + if arr[i].type == "blank" then return true end + end + return false + end + + local function split_list_items(arr) + local acc = {arr[1]} + local res = {} + for i=2,#arr do + if arr[i].type == "list_item" then + table.insert(res, acc) + acc = {arr[i]} + else + table.insert(acc, arr[i]) + end + end + table.insert(res, acc) + return res + end + + local function process_list_item(lines, block) + while lines[#lines].type == "blank" do + table.remove(lines) + end + + local itemtext = lines[1].text + for i=2,#lines do + itemtext = itemtext .. "\n" .. outdent(lines[i].line) + end + if block then + itemtext = block_transform(itemtext, true) + if not itemtext:find("
") then itemtext = indent(itemtext) end
+				return "    
  • " .. itemtext .. "
  • " + else + local lines = split(itemtext) + lines = map(lines, classify) + lines = lists(lines, true) + lines = blocks_to_html(lines, true) + itemtext = table.concat(lines, "\n") + if not itemtext:find("
    ") then itemtext = indent(itemtext) end
    +				return "    
  • " .. itemtext .. "
  • " + end + end + + local block_list = any_blanks(arr) + local items = split_list_items(arr) + local out = "" + for _, item in ipairs(items) do + out = out .. process_list_item(item, block_list) .. "\n" + end + if arr[1].list_type == "numeric" then + return "
      \n" .. out .. "
    " + else + return "
      \n" .. out .. "
    " + end + end + + -- Finds the range of lines composing the first list in the array. A list + -- starts with (^ list_item) or (blank list_item) and ends with + -- (blank* $) or (blank normal). + -- + -- A sublist can start with just (list_item) does not need a blank... + local function find_list(array, sublist) + local function find_list_start(array, sublist) + if array[1].type == "list_item" then return 1 end + if sublist then + for i = 1,#array do + if array[i].type == "list_item" then return i end + end + else + for i = 1, #array-1 do + if array[i].type == "blank" and array[i+1].type == "list_item" then + return i+1 + end + end + end + return nil + end + local function find_list_end(array, start) + local pos = #array + for i = start, #array-1 do + if array[i].type == "blank" and array[i+1].type ~= "list_item" + and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then + pos = i-1 + break + end + end + while pos > start and array[pos].type == "blank" do + pos = pos - 1 + end + return pos + end + + local start = find_list_start(array, sublist) + if not start then return nil end + return start, find_list_end(array, start) + end + + while true do + local start, stop = find_list(array, sublist) + if not start then break end + local text = process_list(splice(array, start, stop)) + local info = { + line = text, + type = "raw", + html = text + } + array = splice(array, start, stop, {info}) + end + + -- Convert any remaining list items to normal + for _,line in ipairs(array) do + if line.type == "list_item" then line.type = "normal" end + end + + return array +end + +-- Find and convert blockquote markers. +function blockquotes(lines) + local function find_blockquote(lines) + local start + for i,line in ipairs(lines) do + if line.type == "blockquote" then + start = i + break + end + end + if not start then return nil end + + local stop = #lines + for i = start+1, #lines do + if lines[i].type == "blank" or lines[i].type == "blockquote" then + elseif lines[i].type == "normal" then + if lines[i-1].type == "blank" then stop = i-1 break end + else + stop = i-1 break + end + end + while lines[stop].type == "blank" do stop = stop - 1 end + return start, stop + end + + local function process_blockquote(lines) + local raw = lines[1].text + for i = 2,#lines do + raw = raw .. "\n" .. lines[i].text + end + local bt = block_transform(raw) + if not bt:find("
    ") then bt = indent(bt) end
    +		return "
    \n " .. bt .. + "\n
    " + end + + while true do + local start, stop = find_blockquote(lines) + if not start then break end + local text = process_blockquote(splice(lines, start, stop)) + local info = { + line = text, + type = "raw", + html = text + } + lines = splice(lines, start, stop, {info}) + end + return lines +end + +-- Find and convert codeblocks. +function codeblocks(lines) + local function find_codeblock(lines) + local start + for i,line in ipairs(lines) do + if line.type == "indented" then start = i break end + end + if not start then return nil end + + local stop = #lines + for i = start+1, #lines do + if lines[i].type ~= "indented" and lines[i].type ~= "blank" then + stop = i-1 + break + end + end + while lines[stop].type == "blank" do stop = stop - 1 end + return start, stop + end + + local function process_codeblock(lines) + local raw = detab(encode_code(outdent(lines[1].line))) + for i = 2,#lines do + raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) + end + return "
    " .. raw .. "\n
    " + end + + while true do + local start, stop = find_codeblock(lines) + if not start then break end + local text = process_codeblock(splice(lines, start, stop)) + local info = { + line = text, + type = "raw", + html = text + } + lines = splice(lines, start, stop, {info}) + end + return lines +end + +-- Convert lines to html code +function blocks_to_html(lines, no_paragraphs) + local out = {} + local i = 1 + while i <= #lines do + local line = lines[i] + if line.type == "ruler" then + table.insert(out, "
    ") + elseif line.type == "raw" then + table.insert(out, line.html) + elseif line.type == "normal" then + local s = line.line + + while i+1 <= #lines and lines[i+1].type == "normal" do + i = i + 1 + s = s .. "\n" .. lines[i].line + end + + if no_paragraphs then + table.insert(out, span_transform(s)) + else + table.insert(out, "

    " .. span_transform(s) .. "

    ") + end + elseif line.type == "header" then + local s = "" .. span_transform(line.text) .. "" + table.insert(out, s) + else + table.insert(out, line.line) + end + i = i + 1 + end + return out +end + +-- Perform all the block level transforms +function block_transform(text, sublist) + local lines = split(text) + lines = map(lines, classify) + lines = headers(lines) + lines = lists(lines, sublist) + lines = codeblocks(lines) + lines = blockquotes(lines) + lines = blocks_to_html(lines) + local text = table.concat(lines, "\n") + return text +end + +-- Debug function for printing a line array to see the result +-- of partial transforms. +function print_lines(lines) + for i, line in ipairs(lines) do + print(i, line.type, line.text or line.line) + end +end + +---------------------------------------------------------------------- +-- Span transform +---------------------------------------------------------------------- + +-- Functions for transforming the text at the span level. + +-- These characters may need to be escaped because they have a special +-- meaning in markdown. +escape_chars = "'\\`*_{}[]()>#+-.!'" +escape_table = {} + +function init_escape_table() + escape_table = {} + for i = 1,#escape_chars do + local c = escape_chars:sub(i,i) + escape_table[c] = hash(c) + end +end + +-- Adds a new escape to the escape table. +function add_escape(text) + if not escape_table[text] then + escape_table[text] = hash(text) + end + return escape_table[text] +end + +-- Escape characters that should not be disturbed by markdown. +function escape_special_chars(text) + local tokens = tokenize_html(text) + + local out = "" + for _, token in ipairs(tokens) do + local t = token.text + if token.type == "tag" then + -- In tags, encode * and _ so they don't conflict with their use in markdown. + t = t:gsub("%*", escape_table["*"]) + t = t:gsub("%_", escape_table["_"]) + else + t = encode_backslash_escapes(t) + end + out = out .. t + end + return out +end + +-- Encode backspace-escaped characters in the markdown source. +function encode_backslash_escapes(t) + for i=1,escape_chars:len() do + local c = escape_chars:sub(i,i) + t = t:gsub("\\%" .. c, escape_table[c]) + end + return t +end + +-- Unescape characters that have been encoded. +function unescape_special_chars(t) + local tin = t + for k,v in pairs(escape_table) do + k = k:gsub("%%", "%%%%") + t = t:gsub(v,k) + end + if t ~= tin then t = unescape_special_chars(t) end + return t +end + +-- Encode/escape certain characters inside Markdown code runs. +-- The point is that in code, these characters are literals, +-- and lose their special Markdown meanings. +function encode_code(s) + s = s:gsub("%&", "&") + s = s:gsub("<", "<") + s = s:gsub(">", ">") + for k,v in pairs(escape_table) do + s = s:gsub("%"..k, v) + end + return s +end + +-- Handle backtick blocks. +function code_spans(s) + s = s:gsub("\\\\", escape_table["\\"]) + s = s:gsub("\\`", escape_table["`"]) + + local pos = 1 + while true do + local start, stop = s:find("`+", pos) + if not start then return s end + local count = stop - start + 1 + -- Find a matching numbert of backticks + local estart, estop = s:find(string.rep("`", count), stop+1) + local brstart = s:find("\n", stop+1) + if estart and (not brstart or estart < brstart) then + local code = s:sub(stop+1, estart-1) + code = code:gsub("^[ \t]+", "") + code = code:gsub("[ \t]+$", "") + code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) + code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) + code = "" .. encode_code(code) .. "" + code = add_escape(code) + s = s:sub(1, start-1) .. code .. s:sub(estop+1) + pos = start + code:len() + else + pos = stop + 1 + end + end + return s +end + +-- Encode alt text... enodes &, and ". +function encode_alt(s) + if not s then return s end + s = s:gsub('&', '&') + s = s:gsub('"', '"') + s = s:gsub('<', '<') + return s +end + +-- Handle image references +function images(text) + local function reference_link(alt, id) + alt = encode_alt(alt:match("%b[]"):sub(2,-2)) + id = id:match("%[(.*)%]"):lower() + if id == "" then id = text:lower() end + link_database[id] = link_database[id] or {} + if not link_database[id].url then return nil end + local url = link_database[id].url or id + url = encode_alt(url) + local title = encode_alt(link_database[id].title) + if title then title = " title=\"" .. title .. "\"" else title = "" end + return add_escape ('' .. alt .. '") + end + + local function inline_link(alt, link) + alt = encode_alt(alt:match("%b[]"):sub(2,-2)) + local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") + url = url or link:match("%(?%)") + url = encode_alt(url) + title = encode_alt(title) + if title then + return add_escape('' .. alt .. '') + else + return add_escape('' .. alt .. '') + end + end + + text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) + text = text:gsub("!(%b[])(%b())", inline_link) + return text +end + +-- Handle anchor references +function anchors(text) + local function reference_link(text, id) + text = text:match("%b[]"):sub(2,-2) + id = id:match("%b[]"):sub(2,-2):lower() + if id == "" then id = text:lower() end + link_database[id] = link_database[id] or {} + if not link_database[id].url then return nil end + local url = link_database[id].url or id + url = encode_alt(url) + local title = encode_alt(link_database[id].title) + if title then title = " title=\"" .. title .. "\"" else title = "" end + return add_escape("") .. text .. add_escape("") + end + + local function inline_link(text, link) + text = text:match("%b[]"):sub(2,-2) + local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") + title = encode_alt(title) + url = url or link:match("%(?%)") or "" + url = encode_alt(url) + if title then + return add_escape("") .. text .. "" + else + return add_escape("") .. text .. add_escape("") + end + end + + text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) + text = text:gsub("(%b[])(%b())", inline_link) + return text +end + +-- Handle auto links, i.e. . +function auto_links(text) + local function link(s) + return add_escape("") .. s .. "" + end + -- Encode chars as a mix of dec and hex entitites to (perhaps) fool + -- spambots. + local function encode_email_address(s) + -- Use a deterministic encoding to make unit testing possible. + -- Code 45% hex, 45% dec, 10% plain. + local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} + local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} + local plain = {code = function(c) return c end, count = 0, rate = 0.1} + local codes = {hex, dec, plain} + local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end + + local out = "" + for i = 1,s:len() do + for _,code in ipairs(codes) do code.count = code.count + code.rate end + if codes[1].count < codes[2].count then swap(codes,1,2) end + if codes[2].count < codes[3].count then swap(codes,2,3) end + if codes[1].count < codes[2].count then swap(codes,1,2) end + + local code = codes[1] + local c = s:sub(i,i) + -- Force encoding of "@" to make email address more invisible. + if c == "@" and code == plain then code = codes[2] end + out = out .. code.code(c) + code.count = code.count - 1 + end + return out + end + local function mail(s) + s = unescape_special_chars(s) + local address = encode_email_address("mailto:" .. s) + local text = encode_email_address(s) + return add_escape("") .. text .. "" + end + -- links + text = text:gsub("<(https?:[^'\">%s]+)>", link) + text = text:gsub("<(ftp:[^'\">%s]+)>", link) + + -- mail + text = text:gsub("%s]+)>", mail) + text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) + return text +end + +-- Encode free standing amps (&) and angles (<)... note that this does not +-- encode free >. +function amps_and_angles(s) + -- encode amps not part of &..; expression + local pos = 1 + while true do + local amp = s:find("&", pos) + if not amp then break end + local semi = s:find(";", amp+1) + local stop = s:find("[ \t\n&]", amp+1) + if not semi or (stop and stop < semi) or (semi - amp) > 15 then + s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) + pos = amp+1 + else + pos = amp+1 + end + end + + -- encode naked <'s + s = s:gsub("<([^a-zA-Z/?$!])", "<%1") + s = s:gsub("<$", "<") + + -- what about >, nothing done in the original markdown source to handle them + return s +end + +-- Handles emphasis markers (* and _) in the text. +function emphasis(text) + for _, s in ipairs {"%*%*", "%_%_"} do + text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "%1") + text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "%1") + end + for _, s in ipairs {"%*", "%_"} do + text = text:gsub(s .. "([^%s_])" .. s, "%1") + text = text:gsub(s .. "([^%s_])" .. s, "%1") + text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "%1") + text = text:gsub(s .. "([^<>_]-[^<>_]-[^<>_]-)" .. s, "%1") + end + return text +end + +-- Handles line break markers in the text. +function line_breaks(text) + return text:gsub(" +\n", "
    \n") +end + +-- Perform all span level transforms. +function span_transform(text) + text = code_spans(text) + text = escape_special_chars(text) + text = images(text) + text = anchors(text) + text = auto_links(text) + text = amps_and_angles(text) + text = emphasis(text) + text = line_breaks(text) + return text +end + +---------------------------------------------------------------------- +-- Markdown +---------------------------------------------------------------------- + +-- Cleanup the text by normalizing some possible variations to make further +-- processing easier. +function cleanup(text) + -- Standardize line endings + text = text:gsub("\r\n", "\n") -- DOS to UNIX + text = text:gsub("\r", "\n") -- Mac to UNIX + + -- Convert all tabs to spaces + text = detab(text) + + -- Strip lines with only spaces and tabs + while true do + local subs + text, subs = text:gsub("\n[ \t]+\n", "\n\n") + if subs == 0 then break end + end + + return "\n" .. text .. "\n" +end + +-- Strips link definitions from the text and stores the data in a lookup table. +function strip_link_definitions(text) + local linkdb = {} + + local function link_def(id, url, title) + id = id:match("%[(.+)%]"):lower() + linkdb[id] = linkdb[id] or {} + linkdb[id].url = url or linkdb[id].url + linkdb[id].title = title or linkdb[id].title + return "" + end + + local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*]+)>?[ \t]*" + local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" + local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" + local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" + + text = text:gsub(def_title1, link_def) + text = text:gsub(def_title2, link_def) + text = text:gsub(def_title3, link_def) + text = text:gsub(def_no_title, link_def) + return text, linkdb +end + +link_database = {} + +-- Main markdown processing function +function markdown(text) + init_hash(text) + init_escape_table() + + text = cleanup(text) + text = protect(text) + text, link_database = strip_link_definitions(text) + text = block_transform(text) + text = unescape_special_chars(text) + return text +end + +---------------------------------------------------------------------- +-- End of module +---------------------------------------------------------------------- + +setfenv(1, _G) +M.lock(M) + +-- Expose markdown function to the world +markdown = M.markdown + +-- Class for parsing command-line options +local OptionParser = {} +OptionParser.__index = OptionParser + +-- Creates a new option parser +function OptionParser:new() + local o = {short = {}, long = {}} + setmetatable(o, self) + return o +end + +-- Calls f() whenever a flag with specified short and long name is encountered +function OptionParser:flag(short, long, f) + local info = {type = "flag", f = f} + if short then self.short[short] = info end + if long then self.long[long] = info end +end + +-- Calls f(param) whenever a parameter flag with specified short and long name is encountered +function OptionParser:param(short, long, f) + local info = {type = "param", f = f} + if short then self.short[short] = info end + if long then self.long[long] = info end +end + +-- Calls f(v) for each non-flag argument +function OptionParser:arg(f) + self.arg = f +end + +-- Runs the option parser for the specified set of arguments. Returns true if all arguments +-- where successfully parsed and false otherwise. +function OptionParser:run(args) + local pos = 1 + while pos <= #args do + local arg = args[pos] + if arg == "--" then + for i=pos+1,#args do + if self.arg then self.arg(args[i]) end + return true + end + end + if arg:match("^%-%-") then + local info = self.long[arg:sub(3)] + if not info then print("Unknown flag: " .. arg) return false end + if info.type == "flag" then + info.f() + pos = pos + 1 + else + param = args[pos+1] + if not param then print("No parameter for flag: " .. arg) return false end + info.f(param) + pos = pos+2 + end + elseif arg:match("^%-") then + for i=2,arg:len() do + local c = arg:sub(i,i) + local info = self.short[c] + if not info then print("Unknown flag: -" .. c) return false end + if info.type == "flag" then + info.f() + else + if i == arg:len() then + param = args[pos+1] + if not param then print("No parameter for flag: -" .. c) return false end + info.f(param) + pos = pos + 1 + else + param = arg:sub(i+1) + info.f(param) + end + break + end + end + pos = pos + 1 + else + if self.arg then self.arg(arg) end + pos = pos + 1 + end + end + return true +end + +-- Handles the case when markdown is run from the command line +local function run_command_line(arg) + -- Generate output for input s given options + local function run(s, options) + s = markdown(s) + if not options.wrap_header then return s end + local header = "" + if options.header then + local f = io.open(options.header) or error("Could not open file: " .. options.header) + header = f:read("*a") + f:close() + else + header = [[ + + + + + TITLE + + + +]] + local title = options.title or s:match("

    (.-)

    ") or s:match("

    (.-)

    ") or + s:match("

    (.-)

    ") or "Untitled" + header = header:gsub("TITLE", title) + if options.inline_style then + local style = "" + local f = io.open(options.stylesheet) + if f then + style = f:read("*a") f:close() + else + error("Could not include style sheet " .. options.stylesheet .. ": File not found") + end + header = header:gsub('', + "") + else + header = header:gsub("STYLESHEET", options.stylesheet) + end + header = header:gsub("CHARSET", options.charset) + end + local footer = "" + if options.footer then + local f = io.open(options.footer) or error("Could not open file: " .. options.footer) + footer = f:read("*a") + f:close() + end + return header .. s .. footer + end + + -- Generate output path name from input path name given options. + local function outpath(path, options) + if options.append then return path .. ".html" end + local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end + m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end + return path .. ".html" + end + + -- Default commandline options + local options = { + wrap_header = true, + header = nil, + footer = nil, + charset = "utf-8", + title = nil, + stylesheet = "default.css", + inline_style = false + } + local help = [[ +Usage: markdown.lua [OPTION] [FILE] +Runs the markdown text markup to HTML converter on each file specified on the +command line. If no files are specified, runs on standard input. + +No header: + -n, --no-wrap Don't wrap the output in ... tags. +Custom header: + -e, --header FILE Use content of FILE for header. + -f, --footer FILE Use content of FILE for footer. +Generated header: + -c, --charset SET Specifies charset (default utf-8). + -i, --title TITLE Specifies title (default from first

    tag). + -s, --style STYLE Specifies style sheet file (default default.css). + -l, --inline-style Include the style sheet file inline in the header. +Generated files: + -a, --append Append .html extension (instead of replacing). +Other options: + -h, --help Print this help text. + -t, --test Run the unit tests. +]] + + local run_stdin = true + local op = OptionParser:new() + op:flag("n", "no-wrap", function () options.wrap_header = false end) + op:param("e", "header", function (x) options.header = x end) + op:param("f", "footer", function (x) options.footer = x end) + op:param("c", "charset", function (x) options.charset = x end) + op:param("i", "title", function(x) options.title = x end) + op:param("s", "style", function(x) options.stylesheet = x end) + op:flag("l", "inline-style", function(x) options.inline_style = true end) + op:flag("a", "append", function() options.append = true end) + op:flag("t", "test", function() + local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") + local f = io.open(n) + if f then + f:close() dofile(n) + else + error("Cannot find markdown-tests.lua") + end + run_stdin = false + end) + op:flag("h", "help", function() print(help) run_stdin = false end) + op:arg(function(path) + local file = io.open(path) or error("Could not open file: " .. path) + local s = file:read("*a") + file:close() + s = run(s, options) + file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) + file:write(s) + file:close() + run_stdin = false + end + ) + + if not op:run(arg) then + print(help) + run_stdin = false + end + + if run_stdin then + local s = io.read("*a") + s = run(s, options) + io.write(s) + end +end + +-- If we are being run from the command-line, act accordingly +if arg and arg[0]:find("markdown%.lua$") then + run_command_line(arg) +else + return markdown +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler.lua new file mode 100644 index 000000000..202b254fd --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler.lua @@ -0,0 +1,181 @@ +--------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Convert between various code representation formats. Atomic +-- converters are written in extenso, others are composed automatically +-- by chaining the atomic ones together in a closure. +-- +-- Supported formats are: +-- +-- * srcfile: the name of a file containing sources. +-- * src: these sources as a single string. +-- * lexstream: a stream of lexemes. +-- * ast: an abstract syntax tree. +-- * proto: a (Yueliang) struture containing a high level +-- representation of bytecode. Largely based on the +-- Proto structure in Lua's VM +-- * bytecode: a string dump of the function, as taken by +-- loadstring() and produced by string.dump(). +-- * function: an executable lua function in RAM. +-- +-------------------------------------------------------------------------------- + +local checks = require 'checks' + +local M = { } + +-------------------------------------------------------------------------------- +-- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can +-- be transformed into a 'b' (but not the other way around). +-- M.sequence goes for numbers to format names, M.order goes from format +-- names to numbers. +-------------------------------------------------------------------------------- +M.sequence = { + 'srcfile', 'src', 'lexstream', 'ast', 'proto', 'bytecode', 'function' } + +local arg_types = { + srcfile = { 'string', '?string' }, + src = { 'string', '?string' }, + lexstream = { 'lexer.stream', '?string' }, + ast = { 'table', '?string' }, + proto = { 'table', '?string' }, + bytecode = { 'string', '?string' }, +} + +if false then + -- if defined, runs on every newly-generated AST + function M.check_ast(ast) + local function rec(x, n, parent) + if not x.lineinfo and parent.lineinfo then + local pp = require 'metalua.pprint' + pp.printf("WARNING: Missing lineinfo in child #%s `%s{...} of node at %s", + n, x.tag or '', tostring(parent.lineinfo)) + end + for i, child in ipairs(x) do + if type(child)=='table' then rec(child, i, x) end + end + end + rec(ast, -1, { }) + end +end + + +M.order= { }; for a,b in pairs(M.sequence) do M.order[b]=a end + +local CONV = { } -- conversion metatable __index + +function CONV :srcfile_to_src(x, name) + checks('metalua.compiler', 'string', '?string') + name = name or '@'..x + local f, msg = io.open (x, 'rb') + if not f then error(msg) end + local r, msg = f :read '*a' + if not r then error("Cannot read file '"..x.."': "..msg) end + f :close() + return r, name +end + +function CONV :src_to_lexstream(src, name) + checks('metalua.compiler', 'string', '?string') + local r = self.parser.lexer :newstream (src, name) + return r, name +end + +function CONV :lexstream_to_ast(lx, name) + checks('metalua.compiler', 'lexer.stream', '?string') + local r = self.parser.chunk(lx) + r.source = name + if M.check_ast then M.check_ast (r) end + return r, name +end + +local bytecode_compiler = nil -- cache to avoid repeated `pcall(require(...))` +local function get_bytecode_compiler() + if bytecode_compiler then return bytecode_compiler else + local status, result = pcall(require, 'metalua.compiler.bytecode') + if status then + bytecode_compiler = result + return result + elseif string.match(result, "not found") then + error "Compilation only available with full Metalua" + else error (result) end + end +end + +function CONV :ast_to_proto(ast, name) + checks('metalua.compiler', 'table', '?string') + return get_bytecode_compiler().ast_to_proto(ast, name), name +end + +function CONV :proto_to_bytecode(proto, name) + return get_bytecode_compiler().proto_to_bytecode(proto), name +end + +function CONV :bytecode_to_function(bc, name) + checks('metalua.compiler', 'string', '?string') + return loadstring(bc, name) +end + +-- Create all sensible combinations +for i=1,#M.sequence do + local src = M.sequence[i] + for j=i+2, #M.sequence do + local dst = M.sequence[j] + local dst_name = src.."_to_"..dst + local my_arg_types = arg_types[src] + local functions = { } + for k=i, j-1 do + local name = M.sequence[k].."_to_"..M.sequence[k+1] + local f = assert(CONV[name], name) + table.insert (functions, f) + end + CONV[dst_name] = function(self, a, b) + checks('metalua.compiler', unpack(my_arg_types)) + for _, f in ipairs(functions) do + a, b = f(self, a, b) + end + return a, b + end + --printf("Created M.%s out of %s", dst_name, table.concat(n, ', ')) + end +end + + +-------------------------------------------------------------------------------- +-- This one goes in the "wrong" direction, cannot be composed. +-------------------------------------------------------------------------------- +function CONV :function_to_bytecode(...) return string.dump(...) end + +function CONV :ast_to_src(...) + require 'metalua.loader' -- ast_to_string isn't written in plain lua + return require 'metalua.compiler.ast_to_src' (...) +end + +local MT = { __index=CONV, __type='metalua.compiler' } + +function M.new() + local parser = require 'metalua.compiler.parser' .new() + local self = { parser = parser } + setmetatable(self, MT) + return self +end + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/ast_to_src.mlua b/Utils/luarocks/share/lua/5.1/metalua/compiler/ast_to_src.mlua new file mode 100644 index 000000000..ca80a12d2 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/ast_to_src.mlua @@ -0,0 +1,682 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +-{ extension ('match', ...) } + +local M = { } +M.__index = M +M.__call = |self, ...| self:run(...) + +local pp=require 'metalua.pprint' + +-------------------------------------------------------------------------------- +-- Instanciate a new AST->source synthetizer +-------------------------------------------------------------------------------- +function M.new () + local self = { + _acc = { }, -- Accumulates pieces of source as strings + current_indent = 0, -- Current level of line indentation + indent_step = " " -- Indentation symbol, normally spaces or '\t' + } + return setmetatable (self, M) +end + +-------------------------------------------------------------------------------- +-- Run a synthetizer on the `ast' arg and return the source as a string. +-- Can also be used as a static method `M.run (ast)'; in this case, +-- a temporary Metizer is instanciated on the fly. +-------------------------------------------------------------------------------- +function M:run (ast) + if not ast then + self, ast = M.new(), self + end + self._acc = { } + self:node (ast) + return table.concat (self._acc) +end + +-------------------------------------------------------------------------------- +-- Accumulate a piece of source file in the synthetizer. +-------------------------------------------------------------------------------- +function M:acc (x) + if x then table.insert (self._acc, x) end +end + +-------------------------------------------------------------------------------- +-- Accumulate an indented newline. +-- Jumps an extra line if indentation is 0, so that +-- toplevel definitions are separated by an extra empty line. +-------------------------------------------------------------------------------- +function M:nl () + if self.current_indent == 0 then self:acc "\n" end + self:acc ("\n" .. self.indent_step:rep (self.current_indent)) +end + +-------------------------------------------------------------------------------- +-- Increase indentation and accumulate a new line. +-------------------------------------------------------------------------------- +function M:nlindent () + self.current_indent = self.current_indent + 1 + self:nl () +end + +-------------------------------------------------------------------------------- +-- Decrease indentation and accumulate a new line. +-------------------------------------------------------------------------------- +function M:nldedent () + self.current_indent = self.current_indent - 1 + self:acc ("\n" .. self.indent_step:rep (self.current_indent)) +end + +-------------------------------------------------------------------------------- +-- Keywords, which are illegal as identifiers. +-------------------------------------------------------------------------------- +local keywords_list = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "if", + "in", "local", "nil", "not", "or", + "repeat", "return", "then", "true", "until", + "while" } +local keywords = { } +for _, kw in pairs(keywords_list) do keywords[kw]=true end + +-------------------------------------------------------------------------------- +-- Return true iff string `id' is a legal identifier name. +-------------------------------------------------------------------------------- +local function is_ident (id) + return string['match'](id, "^[%a_][%w_]*$") and not keywords[id] +end + +-------------------------------------------------------------------------------- +-- Return true iff ast represents a legal function name for +-- syntax sugar ``function foo.bar.gnat() ... end'': +-- a series of nested string indexes, with an identifier as +-- the innermost node. +-------------------------------------------------------------------------------- +local function is_idx_stack (ast) + match ast with + | `Id{ _ } -> return true + | `Index{ left, `String{ _ } } -> return is_idx_stack (left) + | _ -> return false + end +end + +-------------------------------------------------------------------------------- +-- Operator precedences, in increasing order. +-- This is not directly used, it's used to generate op_prec below. +-------------------------------------------------------------------------------- +local op_preprec = { + { "or", "and" }, + { "lt", "le", "eq", "ne" }, + { "concat" }, + { "add", "sub" }, + { "mul", "div", "mod" }, + { "unary", "not", "len" }, + { "pow" }, + { "index" } } + +-------------------------------------------------------------------------------- +-- operator --> precedence table, generated from op_preprec. +-------------------------------------------------------------------------------- +local op_prec = { } + +for prec, ops in ipairs (op_preprec) do + for _, op in ipairs (ops) do + op_prec[op] = prec + end +end + +-------------------------------------------------------------------------------- +-- operator --> source representation. +-------------------------------------------------------------------------------- +local op_symbol = { + add = " + ", sub = " - ", mul = " * ", + div = " / ", mod = " % ", pow = " ^ ", + concat = " .. ", eq = " == ", ne = " ~= ", + lt = " < ", le = " <= ", ["and"] = " and ", + ["or"] = " or ", ["not"] = "not ", len = "# " } + +-------------------------------------------------------------------------------- +-- Accumulate the source representation of AST `node' in +-- the synthetizer. Most of the work is done by delegating to +-- the method having the name of the AST tag. +-- If something can't be converted to normal sources, it's +-- instead dumped as a `-{ ... }' splice in the source accumulator. +-------------------------------------------------------------------------------- +function M:node (node) + assert (self~=M and self._acc) + if node==nil then self:acc'<>' + elseif not self.custom_printer or not self.custom_printer (self, node) then + if not node.tag then -- tagless (henceunindented) block. + self:list (node, self.nl) + else + local f = M[node.tag] + if type (f) == "function" then -- Delegate to tag method. + f (self, node, unpack (node)) + elseif type (f) == "string" then -- tag string. + self:acc (f) + else -- No appropriate method, fall back to splice dumping. + -- This cannot happen in a plain Lua AST. + self:acc " -{ " + self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1}), 80) + self:acc " }" + end + end + end +end + +function M:block(body) + if not self.custom_printer or not self.custom_printer (self, body) then + self:nlindent () + self:list (body, self.nl) + self:nldedent () + end +end + +-------------------------------------------------------------------------------- +-- Convert every node in the AST list `list' passed as 1st arg. +-- `sep' is an optional separator to be accumulated between each list element, +-- it can be a string or a synth method. +-- `start' is an optional number (default == 1), indicating which is the +-- first element of list to be converted, so that we can skip the begining +-- of a list. +-------------------------------------------------------------------------------- +function M:list (list, sep, start) + for i = start or 1, # list do + self:node (list[i]) + if list[i + 1] then + if not sep then + elseif type (sep) == "function" then sep (self) + elseif type (sep) == "string" then self:acc (sep) + else error "Invalid list separator" end + end + end +end + +-------------------------------------------------------------------------------- +-- +-- Tag methods. +-- ------------ +-- +-- Specific AST node dumping methods, associated to their node kinds +-- by their name, which is the corresponding AST tag. +-- synth:node() is in charge of delegating a node's treatment to the +-- appropriate tag method. +-- +-- Such tag methods are called with the AST node as 1st arg. +-- As a convenience, the n node's children are passed as args #2 ... n+1. +-- +-- There are several things that could be refactored into common subroutines +-- here: statement blocks dumping, function dumping... +-- However, given their small size and linear execution +-- (they basically perform series of :acc(), :node(), :list(), +-- :nl(), :nlindent() and :nldedent() calls), it seems more readable +-- to avoid multiplication of such tiny functions. +-- +-- To make sense out of these, you need to know metalua's AST syntax, as +-- found in the reference manual or in metalua/doc/ast.txt. +-- +-------------------------------------------------------------------------------- + +function M:Do (node) + self:acc "do" + self:block (node) + self:acc "end" +end + +function M:Set (node) + match node with + | `Set{ { `Index{ lhs, `String{ method } } }, + { `Function{ { `Id "self", ... } == params, body } } } + if is_idx_stack (lhs) and is_ident (method) -> + -- ``function foo:bar(...) ... end'' -- + self:acc "function " + self:node (lhs) + self:acc ":" + self:acc (method) + self:acc " (" + self:list (params, ", ", 2) + self:acc ")" + self:block (body) + self:acc "end" + + | `Set{ { lhs }, { `Function{ params, body } } } if is_idx_stack (lhs) -> + -- ``function foo(...) ... end'' -- + self:acc "function " + self:node (lhs) + self:acc " (" + self:list (params, ", ") + self:acc ")" + self:block (body) + self:acc "end" + + | `Set{ { `Id{ lhs1name } == lhs1, ... } == lhs, rhs } + if not is_ident (lhs1name) -> + -- ``foo, ... = ...'' when foo is *not* a valid identifier. + -- In that case, the spliced 1st variable must get parentheses, + -- to be distinguished from a statement splice. + -- This cannot happen in a plain Lua AST. + self:acc "(" + self:node (lhs1) + self:acc ")" + if lhs[2] then -- more than one lhs variable + self:acc ", " + self:list (lhs, ", ", 2) + end + self:acc " = " + self:list (rhs, ", ") + + | `Set{ lhs, rhs } -> + -- ``... = ...'', no syntax sugar -- + self:list (lhs, ", ") + self:acc " = " + self:list (rhs, ", ") + | `Set{ lhs, rhs, annot } -> + -- ``... = ...'', no syntax sugar, annotation -- + local n = #lhs + for i=1,n do + local ell, a = lhs[i], annot[i] + self:node (ell) + if a then + self:acc ' #' + self:node(a) + end + if i~=n then self:acc ', ' end + end + self:acc " = " + self:list (rhs, ", ") + end +end + +function M:While (node, cond, body) + self:acc "while " + self:node (cond) + self:acc " do" + self:block (body) + self:acc "end" +end + +function M:Repeat (node, body, cond) + self:acc "repeat" + self:block (body) + self:acc "until " + self:node (cond) +end + +function M:If (node) + for i = 1, #node-1, 2 do + -- for each ``if/then'' and ``elseif/then'' pair -- + local cond, body = node[i], node[i+1] + self:acc (i==1 and "if " or "elseif ") + self:node (cond) + self:acc " then" + self:block (body) + end + -- odd number of children --> last one is an `else' clause -- + if #node%2 == 1 then + self:acc "else" + self:block (node[#node]) + end + self:acc "end" +end + +function M:Fornum (node, var, first, last) + local body = node[#node] + self:acc "for " + self:node (var) + self:acc " = " + self:node (first) + self:acc ", " + self:node (last) + if #node==5 then -- 5 children --> child #4 is a step increment. + self:acc ", " + self:node (node[4]) + end + self:acc " do" + self:block (body) + self:acc "end" +end + +function M:Forin (node, vars, generators, body) + self:acc "for " + self:list (vars, ", ") + self:acc " in " + self:list (generators, ", ") + self:acc " do" + self:block (body) + self:acc "end" +end + +function M:Local (node, lhs, rhs, annots) + if next (lhs) then + self:acc "local " + if annots then + local n = #lhs + for i=1, n do + self:node (lhs) + local a = annots[i] + if a then + self:acc ' #' + self:node (a) + end + if i~=n then self:acc ', ' end + end + else + self:list (lhs, ", ") + end + if rhs[1] then + self:acc " = " + self:list (rhs, ", ") + end + else -- Can't create a local statement with 0 variables in plain Lua + self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1, fix_indent=2})) + end +end + +function M:Localrec (node, lhs, rhs) + match node with + | `Localrec{ { `Id{name} }, { `Function{ params, body } } } + if is_ident (name) -> + -- ``local function name() ... end'' -- + self:acc "local function " + self:acc (name) + self:acc " (" + self:list (params, ", ") + self:acc ")" + self:block (body) + self:acc "end" + + | _ -> + -- Other localrec are unprintable ==> splice them -- + -- This cannot happen in a plain Lua AST. -- + self:acc "-{ " + self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1, fix_indent=2})) + self:acc " }" + end +end + +function M:Call (node, f) + -- single string or table literal arg ==> no need for parentheses. -- + local parens + match node with + | `Call{ _, `String{_} } + | `Call{ _, `Table{...}} -> parens = false + | _ -> parens = true + end + self:node (f) + self:acc (parens and " (" or " ") + self:list (node, ", ", 2) -- skip `f'. + self:acc (parens and ")") +end + +function M:Invoke (node, f, method) + -- single string or table literal arg ==> no need for parentheses. -- + local parens + match node with + | `Invoke{ _, _, `String{_} } + | `Invoke{ _, _, `Table{...}} -> parens = false + | _ -> parens = true + end + self:node (f) + self:acc ":" + self:acc (method[1]) + self:acc (parens and " (" or " ") + self:list (node, ", ", 3) -- Skip args #1 and #2, object and method name. + self:acc (parens and ")") +end + +function M:Return (node) + self:acc "return " + self:list (node, ", ") +end + +M.Break = "break" +M.Nil = "nil" +M.False = "false" +M.True = "true" +M.Dots = "..." + +function M:Number (node, n) + self:acc (tostring (n)) +end + +function M:String (node, str) + -- format "%q" prints '\n' in an umpractical way IMO, + -- so this is fixed with the :gsub( ) call. + self:acc (string.format ("%q", str):gsub ("\\\n", "\\n")) +end + +function M:Function (node, params, body, annots) + self:acc "function (" + if annots then + local n = #params + for i=1,n do + local p, a = params[i], annots[i] + self:node(p) + if annots then + self:acc " #" + self:node(a) + end + if i~=n then self:acc ', ' end + end + else + self:list (params, ", ") + end + self:acc ")" + self:block (body) + self:acc "end" +end + +function M:Table (node) + if not node[1] then self:acc "{ }" else + self:acc "{" + if #node > 1 then self:nlindent () else self:acc " " end + for i, elem in ipairs (node) do + match elem with + | `Pair{ `String{ key }, value } if is_ident (key) -> + -- ``key = value''. -- + self:acc (key) + self:acc " = " + self:node (value) + + | `Pair{ key, value } -> + -- ``[key] = value''. -- + self:acc "[" + self:node (key) + self:acc "] = " + self:node (value) + + | _ -> + -- ``value''. -- + self:node (elem) + end + if node [i+1] then + self:acc "," + self:nl () + end + end + if #node > 1 then self:nldedent () else self:acc " " end + self:acc "}" + end +end + +function M:Op (node, op, a, b) + -- Transform ``not (a == b)'' into ``a ~= b''. -- + match node with + | `Op{ "not", `Op{ "eq", _a, _b } } + | `Op{ "not", `Paren{ `Op{ "eq", _a, _b } } } -> + op, a, b = "ne", _a, _b + | _ -> + end + + if b then -- binary operator. + local left_paren, right_paren + match a with + | `Op{ op_a, ...} if op_prec[op] >= op_prec[op_a] -> left_paren = true + | _ -> left_paren = false + end + + match b with -- FIXME: might not work with right assoc operators ^ and .. + | `Op{ op_b, ...} if op_prec[op] >= op_prec[op_b] -> right_paren = true + | _ -> right_paren = false + end + + self:acc (left_paren and "(") + self:node (a) + self:acc (left_paren and ")") + + self:acc (op_symbol [op]) + + self:acc (right_paren and "(") + self:node (b) + self:acc (right_paren and ")") + + else -- unary operator. + local paren + match a with + | `Op{ op_a, ... } if op_prec[op] >= op_prec[op_a] -> paren = true + | _ -> paren = false + end + self:acc (op_symbol[op]) + self:acc (paren and "(") + self:node (a) + self:acc (paren and ")") + end +end + +function M:Paren (node, content) + self:acc "(" + self:node (content) + self:acc ")" +end + +function M:Index (node, table, key) + local paren_table + -- Check precedence, see if parens are needed around the table -- + match table with + | `Op{ op, ... } if op_prec[op] < op_prec.index -> paren_table = true + | _ -> paren_table = false + end + + self:acc (paren_table and "(") + self:node (table) + self:acc (paren_table and ")") + + match key with + | `String{ field } if is_ident (field) -> + -- ``table.key''. -- + self:acc "." + self:acc (field) + | _ -> + -- ``table [key]''. -- + self:acc "[" + self:node (key) + self:acc "]" + end +end + +function M:Id (node, name) + if is_ident (name) then + self:acc (name) + else -- Unprintable identifier, fall back to splice representation. + -- This cannot happen in a plain Lua AST. + self:acc "-{`Id " + self:String (node, name) + self:acc "}" + end +end + + +M.TDyn = '*' +M.TDynbar = '**' +M.TPass = 'pass' +M.TField = 'field' +M.TIdbar = M.TId +M.TReturn = M.Return + + +function M:TId (node, name) self:acc(name) end + + +function M:TCatbar(node, te, tebar) + self:acc'(' + self:node(te) + self:acc'|' + self:tebar(tebar) + self:acc')' +end + +function M:TFunction(node, p, r) + self:tebar(p) + self:acc '->' + self:tebar(r) +end + +function M:TTable (node, default, pairs) + self:acc '[' + self:list (pairs, ', ') + if default.tag~='TField' then + self:acc '|' + self:node (default) + end + self:acc ']' +end + +function M:TPair (node, k, v) + self:node (k) + self:acc '=' + self:node (v) +end + +function M:TIdbar (node, name) + self :acc (name) +end + +function M:TCatbar (node, a, b) + self:node(a) + self:acc ' ++ ' + self:node(b) +end + +function M:tebar(node) + if node.tag then self:node(node) else + self:acc '(' + self:list(node, ', ') + self:acc ')' + end +end + +function M:TUnkbar(node, name) + self:acc '~~' + self:acc (name) +end + +function M:TUnk(node, name) + self:acc '~' + self:acc (name) +end + +for name, tag in pairs{ const='TConst', var='TVar', currently='TCurrently', just='TJust' } do + M[tag] = function(self, node, te) + self:acc (name..' ') + self:node(te) + end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode.lua new file mode 100644 index 000000000..b3afbdb73 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode.lua @@ -0,0 +1,29 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local compile = require 'metalua.compiler.bytecode.compile' +local ldump = require 'metalua.compiler.bytecode.ldump' + +local M = { } + +M.ast_to_proto = compile.ast_to_proto +M.proto_to_bytecode = ldump.dump_string +M.proto_to_file = ldump.dump_file + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/compile.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/compile.lua new file mode 100644 index 000000000..011517f3d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/compile.lua @@ -0,0 +1,1263 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Kein-Hong Man, Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang +-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua +-- +------------------------------------------------------------------------------- + +---------------------------------------------------------------------- +-- +-- This code mainly results from the borrowing, then ruthless abuse, of +-- Yueliang's implementation of Lua 5.0 compiler. +-- +--------------------------------------------------------------------- + +local pp = require 'metalua.pprint' + +local luaK = require 'metalua.compiler.bytecode.lcode' +local luaP = require 'metalua.compiler.bytecode.lopcodes' + +local debugf = function() end +--local debugf=printf + +local stat = { } +local expr = { } + +local M = { } + +M.MAX_INT = 2147483645 -- INT_MAX-2 for 32-bit systems (llimits.h) +M.MAXVARS = 200 -- (llimits.h) +M.MAXUPVALUES = 32 -- (llimits.h) +M.MAXPARAMS = 100 -- (llimits.h) +M.LUA_MAXPARSERLEVEL = 200 -- (llimits.h) + +-- from lobject.h +M.VARARG_HASARG = 1 +M.VARARG_ISVARARG = 2 +M.VARARG_NEEDSARG = 4 + +local function hasmultret (k) + return k=="VCALL" or k=="VVARARG" +end + +----------------------------------------------------------------------- +-- Some ASTs take expression lists as children; it should be +-- acceptible to give an expression instead, and to automatically +-- interpret it as a single element list. That's what does this +-- function, adding a surrounding list iff needed. +-- +-- WARNING: "Do" is the tag for chunks, which are essentially lists. +-- Therefore, we don't listify stuffs with a "Do" tag. +----------------------------------------------------------------------- +local function ensure_list (ast) + return ast.tag and ast.tag ~= "Do" and {ast} or ast end + +----------------------------------------------------------------------- +-- Get a localvar structure { varname, startpc, endpc } from a +-- (zero-based) index of active variable. The catch is: don't get +-- confused between local index and active index. +-- +-- locvars[x] contains { varname, startpc, endpc }; +-- actvar[i] contains the index of the variable in locvars +----------------------------------------------------------------------- +local function getlocvar (fs, i) + return fs.f.locvars[fs.actvar[i]] +end + +local function removevars (fs, tolevel) + while fs.nactvar > tolevel do + fs.nactvar = fs.nactvar - 1 + -- There may be dummy locvars due to expr.Stat + -- FIXME: strange that they didn't disappear?! + local locvar = getlocvar (fs, fs.nactvar) + --printf("[REMOVEVARS] removing var #%i = %s", fs.nactvar, + -- locvar and tostringv(locvar) or "") + if locvar then locvar.endpc = fs.pc end + end +end + +----------------------------------------------------------------------- +-- [f] has a list of all its local variables, active and inactive. +-- Some local vars can correspond to the same register, if they exist +-- in different scopes. +-- [fs.nlocvars] is the total number of local variables, not to be +-- confused with [fs.nactvar] the numebr of variables active at the +-- current PC. +-- At this stage, the existence of the variable is not yet aknowledged, +-- since [fs.nactvar] and [fs.freereg] aren't updated. +----------------------------------------------------------------------- +local function registerlocalvar (fs, varname) + --debugf("[locvar: %s = reg %i]", varname, fs.nlocvars) + local f = fs.f + f.locvars[fs.nlocvars] = { } -- LocVar + f.locvars[fs.nlocvars].varname = varname + local nlocvars = fs.nlocvars + fs.nlocvars = fs.nlocvars + 1 + return nlocvars +end + +----------------------------------------------------------------------- +-- update the active vars counter in [fs] by adding [nvars] of them, +-- and sets those variables' [startpc] to the current [fs.pc]. +-- These variables were allready created, but not yet counted, by +-- new_localvar. +----------------------------------------------------------------------- +local function adjustlocalvars (fs, nvars) + --debugf("adjustlocalvars, nvars=%i, previous fs.nactvar=%i,".. + -- " #locvars=%i, #actvar=%i", + -- nvars, fs.nactvar, #fs.f.locvars, #fs.actvar) + + fs.nactvar = fs.nactvar + nvars + for i = nvars, 1, -1 do + --printf ("adjusting actvar #%i", fs.nactvar - i) + getlocvar (fs, fs.nactvar - i).startpc = fs.pc + end +end + +------------------------------------------------------------------------ +-- check whether, in an assignment to a local variable, the local variable +-- is needed in a previous assignment (to a table). If so, save original +-- local value in a safe place and use this safe copy in the previous +-- assignment. +------------------------------------------------------------------------ +local function check_conflict (fs, lh, v) + local extra = fs.freereg -- eventual position to save local variable + local conflict = false + while lh do + if lh.v.k == "VINDEXED" then + if lh.v.info == v.info then -- conflict? + conflict = true + lh.v.info = extra -- previous assignment will use safe copy + end + if lh.v.aux == v.info then -- conflict? + conflict = true + lh.v.aux = extra -- previous assignment will use safe copy + end + end + lh = lh.prev + end + if conflict then + luaK:codeABC (fs, "OP_MOVE", fs.freereg, v.info, 0) -- make copy + luaK:reserveregs (fs, 1) + end +end + +----------------------------------------------------------------------- +-- Create an expdesc. To be updated when expdesc is lua-ified. +----------------------------------------------------------------------- +local function init_exp (e, k, i) + e.f, e.t, e.k, e.info = luaK.NO_JUMP, luaK.NO_JUMP, k, i end + +----------------------------------------------------------------------- +-- Reserve the string in tthe constant pool, and return an expdesc +-- referring to it. +----------------------------------------------------------------------- +local function codestring (fs, e, str) + --printf( "codestring(%s)", disp.ast(str)) + init_exp (e, "VK", luaK:stringK (fs, str)) +end + +----------------------------------------------------------------------- +-- search for a local variable named [name] in the function being +-- built by [fs]. Doesn't try to visit upvalues. +----------------------------------------------------------------------- +local function searchvar (fs, name) + for i = fs.nactvar - 1, 0, -1 do + -- Because of expr.Stat, there can be some actvars which don't + -- correspond to any locvar. Hence the checking for locvar's + -- nonnilness before getting the varname. + local locvar = getlocvar(fs, i) + if locvar and name == locvar.varname then + --printf("Found local var: %s; i = %i", tostringv(locvar), i) + return i + end + end + return -1 -- not found +end + +----------------------------------------------------------------------- +-- create and return a new proto [f] +----------------------------------------------------------------------- +local function newproto () + local f = {} + f.k = {} + f.sizek = 0 + f.p = {} + f.sizep = 0 + f.code = {} + f.sizecode = 0 + f.sizelineinfo = 0 + f.sizeupvalues = 0 + f.nups = 0 + f.upvalues = {} + f.numparams = 0 + f.is_vararg = 0 + f.maxstacksize = 0 + f.lineinfo = {} + f.sizelocvars = 0 + f.locvars = {} + f.lineDefined = 0 + f.source = nil + return f +end + +------------------------------------------------------------------------ +-- create and return a function state [new_fs] as a sub-funcstate of [fs]. +------------------------------------------------------------------------ +local function open_func (old_fs) + local new_fs = { } + new_fs.upvalues = { } + new_fs.actvar = { } + local f = newproto () + new_fs.f = f + new_fs.prev = old_fs -- linked list of funcstates + new_fs.pc = 0 + new_fs.lasttarget = -1 + new_fs.jpc = luaK.NO_JUMP + new_fs.freereg = 0 + new_fs.nk = 0 + new_fs.h = {} -- constant table; was luaH_new call + new_fs.np = 0 + new_fs.nlocvars = 0 + new_fs.nactvar = 0 + new_fs.bl = nil + new_fs.nestlevel = old_fs and old_fs.nestlevel or 0 + f.maxstacksize = 2 -- registers 0/1 are always valid + new_fs.lastline = 0 + new_fs.forward_gotos = { } + new_fs.labels = { } + return new_fs +end + +------------------------------------------------------------------------ +-- Finish to set up [f] according to final state of [fs] +------------------------------------------------------------------------ +local function close_func (fs) + local f = fs.f + --printf("[CLOSE_FUNC] remove any remaining var") + removevars (fs, 0) + luaK:ret (fs, 0, 0) + f.sizecode = fs.pc + f.sizelineinfo = fs.pc + f.sizek = fs.nk + f.sizep = fs.np + f.sizelocvars = fs.nlocvars + f.sizeupvalues = f.nups + assert (fs.bl == nil) + if next(fs.forward_gotos) then + local x = pp.tostring(fs.forward_gotos) + error ("Unresolved goto: "..x) + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function pushclosure(fs, func, v) + local f = fs.f + f.p [fs.np] = func.f + fs.np = fs.np + 1 + init_exp (v, "VRELOCABLE", luaK:codeABx (fs, "OP_CLOSURE", 0, fs.np - 1)) + for i = 0, func.f.nups - 1 do + local o = (func.upvalues[i].k == "VLOCAL") and "OP_MOVE" or "OP_GETUPVAL" + luaK:codeABC (fs, o, 0, func.upvalues[i].info, 0) + end +end + +------------------------------------------------------------------------ +-- FIXME: is there a need for f=fs.f? if yes, why not always using it? +------------------------------------------------------------------------ +local function indexupvalue(fs, name, v) + local f = fs.f + for i = 0, f.nups - 1 do + if fs.upvalues[i].k == v.k and fs.upvalues[i].info == v.info then + assert(fs.f.upvalues[i] == name) + return i + end + end + -- new one + f.upvalues[f.nups] = name + assert (v.k == "VLOCAL" or v.k == "VUPVAL") + fs.upvalues[f.nups] = { k = v.k; info = v.info } + local nups = f.nups + f.nups = f.nups + 1 + return nups +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function markupval(fs, level) + local bl = fs.bl + while bl and bl.nactvar > level do bl = bl.previous end + if bl then bl.upval = true end +end + + +--for debug only +--[[ +local function bldepth(fs) + local i, x= 1, fs.bl + while x do i=i+1; x=x.previous end + return i +end +--]] + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function enterblock (fs, bl, isbreakable) + bl.breaklist = luaK.NO_JUMP + bl.isbreakable = isbreakable + bl.nactvar = fs.nactvar + bl.upval = false + bl.previous = fs.bl + fs.bl = bl + assert (fs.freereg == fs.nactvar) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function leaveblock (fs) + local bl = fs.bl + fs.bl = bl.previous + --printf("[LEAVEBLOCK] Removing vars...") + removevars (fs, bl.nactvar) + --printf("[LEAVEBLOCK] ...Vars removed") + if bl.upval then + luaK:codeABC (fs, "OP_CLOSE", bl.nactvar, 0, 0) + end + -- a block either controls scope or breaks (never both) + assert (not bl.isbreakable or not bl.upval) + assert (bl.nactvar == fs.nactvar) + fs.freereg = fs.nactvar -- free registers + luaK:patchtohere (fs, bl.breaklist) +end + + +------------------------------------------------------------------------ +-- read a list of expressions from a list of ast [astlist] +-- starts at the [offset]th element of the list (defaults to 1) +------------------------------------------------------------------------ +local function explist(fs, astlist, v, offset) + offset = offset or 1 + if #astlist < offset then error "I don't handle empty expr lists yet" end + --printf("[EXPLIST] about to precompile 1st element %s", disp.ast(astlist[offset])) + expr.expr (fs, astlist[offset], v) + --printf("[EXPLIST] precompiled first element v=%s", tostringv(v)) + for i = offset+1, #astlist do + luaK:exp2nextreg (fs, v) + --printf("[EXPLIST] flushed v=%s", tostringv(v)) + expr.expr (fs, astlist[i], v) + --printf("[EXPLIST] precompiled element v=%s", tostringv(v)) + end + return #astlist - offset + 1 +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function funcargs (fs, ast, v, idx_from) + local args = { } -- expdesc + local nparams + if #ast < idx_from then args.k = "VVOID" else + explist(fs, ast, args, idx_from) + luaK:setmultret(fs, args) + end + assert(v.k == "VNONRELOC") + local base = v.info -- base register for call + if hasmultret(args.k) then nparams = luaK.LUA_MULTRET else -- open call + if args.k ~= "VVOID" then + luaK:exp2nextreg(fs, args) end -- close last argument + nparams = fs.freereg - (base + 1) + end + init_exp(v, "VCALL", luaK:codeABC(fs, "OP_CALL", base, nparams + 1, 2)) + if ast.lineinfo then + luaK:fixline(fs, ast.lineinfo.first.line) + else + luaK:fixline(fs, ast.line) + end + fs.freereg = base + 1 -- call remove function and arguments and leaves + -- (unless changed) one result +end + +------------------------------------------------------------------------ +-- calculates log value for encoding the hash portion's size +------------------------------------------------------------------------ +local function log2(x) + -- math result is always one more than lua0_log2() + local mn, ex = math.frexp(x) + return ex - 1 +end + +------------------------------------------------------------------------ +-- converts an integer to a "floating point byte", represented as +-- (mmmmmxxx), where the real value is (xxx) * 2^(mmmmm) +------------------------------------------------------------------------ + +-- local function int2fb(x) +-- local m = 0 -- mantissa +-- while x >= 8 do x = math.floor((x + 1) / 2); m = m + 1 end +-- return m * 8 + x +-- end + +local function int2fb(x) + local e = 0 + while x >= 16 do + x = math.floor ( (x+1) / 2) + e = e+1 + end + if x<8 then return x + else return (e+1) * 8 + x - 8 end +end + + +------------------------------------------------------------------------ +-- FIXME: to be unified with singlevar +------------------------------------------------------------------------ +local function singlevaraux(fs, n, var, base) +--[[ +print("\n\nsinglevaraux: fs, n, var, base") +printv(fs) +printv(n) +printv(var) +printv(base) +print("\n") +--]] + if fs == nil then -- no more levels? + init_exp(var, "VGLOBAL", luaP.NO_REG) -- default is global variable + return "VGLOBAL" + else + local v = searchvar(fs, n) -- look up at current level + if v >= 0 then + init_exp(var, "VLOCAL", v) + if not base then + markupval(fs, v) -- local will be used as an upval + end + else -- not found at current level; try upper one + if singlevaraux(fs.prev, n, var, false) == "VGLOBAL" then + return "VGLOBAL" end + var.info = indexupvalue (fs, n, var) + var.k = "VUPVAL" + return "VUPVAL" + end + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function singlevar(fs, varname, var) + if singlevaraux(fs, varname, var, true) == "VGLOBAL" then + var.info = luaK:stringK (fs, varname) end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function new_localvar (fs, name, n) + assert (type (name) == "string") + if fs.nactvar + n > M.MAXVARS then error ("too many local vars") end + fs.actvar[fs.nactvar + n] = registerlocalvar (fs, name) + --printf("[NEW_LOCVAR] %i = %s", fs.nactvar+n, name) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function parlist (fs, ast_params) + local dots = (#ast_params > 0 and ast_params[#ast_params].tag == "Dots") + local nparams = dots and #ast_params - 1 or #ast_params + for i = 1, nparams do + assert (ast_params[i].tag == "Id", "Function parameters must be Ids") + new_localvar (fs, ast_params[i][1], i-1) + end + -- from [code_param]: + --checklimit (fs, fs.nactvar, self.M.MAXPARAMS, "parameters") + fs.f.numparams = fs.nactvar + fs.f.is_vararg = dots and M.VARARG_ISVARARG or 0 + adjustlocalvars (fs, nparams) + fs.f.numparams = fs.nactvar --FIXME vararg must be taken in account + luaK:reserveregs (fs, fs.nactvar) -- reserve register for parameters +end + +------------------------------------------------------------------------ +-- if there's more variables than expressions in an assignment, +-- some assignations to nil are made for extraneous vars. +-- Also handles multiret functions +------------------------------------------------------------------------ +local function adjust_assign (fs, nvars, nexps, e) + local extra = nvars - nexps + if hasmultret (e.k) then + extra = extra+1 -- includes call itself + if extra <= 0 then extra = 0 end + luaK:setreturns(fs, e, extra) -- call provides the difference + if extra > 1 then luaK:reserveregs(fs, extra-1) end + else + if e.k ~= "VVOID" then + luaK:exp2nextreg(fs, e) end -- close last expression + if extra > 0 then + local reg = fs.freereg + luaK:reserveregs(fs, extra) + luaK:_nil(fs, reg, extra) + end + end +end + + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function enterlevel (fs) + fs.nestlevel = fs.nestlevel + 1 + assert (fs.nestlevel <= M.LUA_MAXPARSERLEVEL, "too many syntax levels") +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function leavelevel (fs) + fs.nestlevel = fs.nestlevel - 1 +end + +------------------------------------------------------------------------ +-- Parse conditions in if/then/else, while, repeat +------------------------------------------------------------------------ +local function cond (fs, ast) + local v = { } + expr.expr(fs, ast, v) -- read condition + if v.k == "VNIL" then v.k = "VFALSE" end -- 'falses' are all equal here + luaK:goiftrue (fs, v) + return v.f +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function chunk (fs, ast) + enterlevel (fs) + assert (not ast.tag) + for i=1, #ast do + stat.stat (fs, ast[i]); + fs.freereg = fs.nactvar + end + leavelevel (fs) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function block (fs, ast) + local bl = {} + enterblock (fs, bl, false) + for i=1, #ast do + stat.stat (fs, ast[i]) + fs.freereg = fs.nactvar + end + assert (bl.breaklist == luaK.NO_JUMP) + leaveblock (fs) +end + +------------------------------------------------------------------------ +-- Forin / Fornum body parser +-- [fs] +-- [body] +-- [base] +-- [nvars] +-- [isnum] +------------------------------------------------------------------------ +local function forbody (fs, ast_body, base, nvars, isnum) + local bl = {} -- BlockCnt + adjustlocalvars (fs, 3) -- control variables + local prep = + isnum and luaK:codeAsBx (fs, "OP_FORPREP", base, luaK.NO_JUMP) + or luaK:jump (fs) + enterblock (fs, bl, false) -- loop block + adjustlocalvars (fs, nvars) -- scope for declared variables + luaK:reserveregs (fs, nvars) + block (fs, ast_body) + leaveblock (fs) + --luaK:patchtohere (fs, prep-1) + luaK:patchtohere (fs, prep) + local endfor = + isnum and luaK:codeAsBx (fs, "OP_FORLOOP", base, luaK.NO_JUMP) + or luaK:codeABC (fs, "OP_TFORLOOP", base, 0, nvars) + luaK:fixline (fs, ast_body.line) -- pretend that 'OP_FOR' starts the loop + luaK:patchlist (fs, isnum and endfor or luaK:jump(fs), prep + 1) +end + + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function recfield (fs, ast, cc) + local reg = fs.freereg + local key, val = {}, {} -- expdesc + --FIXME: expr + exp2val = index --> + -- check reduncancy between exp2val and exp2rk + cc.nh = cc.nh + 1 + expr.expr(fs, ast[1], key); + luaK:exp2val (fs, key) + local keyreg = luaK:exp2RK (fs, key) + expr.expr(fs, ast[2], val) + local valreg = luaK:exp2RK (fs, val) + luaK:codeABC(fs, "OP_SETTABLE", cc.t.info, keyreg, valreg) + fs.freereg = reg -- free registers +end + + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function listfield(fs, ast, cc) + expr.expr(fs, ast, cc.v) + assert (cc.na <= luaP.MAXARG_Bx) -- FIXME check <= or < + cc.na = cc.na + 1 + cc.tostore = cc.tostore + 1 +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +local function closelistfield(fs, cc) + if cc.v.k == "VVOID" then return end -- there is no list item + luaK:exp2nextreg(fs, cc.v) + cc.v.k = "VVOID" + if cc.tostore == luaP.LFIELDS_PER_FLUSH then + luaK:setlist (fs, cc.t.info, cc.na, cc.tostore) + cc.tostore = 0 + end +end + +------------------------------------------------------------------------ +-- The last field might be a call to a multireturn function. In that +-- case, we must unfold all of its results into the list. +------------------------------------------------------------------------ +local function lastlistfield(fs, cc) + if cc.tostore == 0 then return end + if hasmultret (cc.v.k) then + luaK:setmultret(fs, cc.v) + luaK:setlist (fs, cc.t.info, cc.na, luaK.LUA_MULTRET) + cc.na = cc.na - 1 + else + if cc.v.k ~= "VVOID" then luaK:exp2nextreg(fs, cc.v) end + luaK:setlist (fs, cc.t.info, cc.na, cc.tostore) + end +end +------------------------------------------------------------------------ +------------------------------------------------------------------------ +-- +-- Statement parsers table +-- +------------------------------------------------------------------------ +------------------------------------------------------------------------ + +function stat.stat (fs, ast) + if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end + --debugf (" - Statement %s", table.tostring (ast) ) + + if not ast.tag then chunk (fs, ast) else + + local parser = stat [ast.tag] + if not parser then + error ("A statement cannot have tag `"..ast.tag) end + parser (fs, ast) + end + --debugf (" - /Statement `%s", ast.tag) +end + +------------------------------------------------------------------------ + +stat.Do = block + +------------------------------------------------------------------------ + +function stat.Break (fs, ast) + -- if ast.lineinfo then fs.lastline = ast.lineinfo.last.line + local bl, upval = fs.bl, false + while bl and not bl.isbreakable do + if bl.upval then upval = true end + bl = bl.previous + end + assert (bl, "no loop to break") + if upval then luaK:codeABC(fs, "OP_CLOSE", bl.nactvar, 0, 0) end + bl.breaklist = luaK:concat(fs, bl.breaklist, luaK:jump(fs)) +end + +------------------------------------------------------------------------ + +function stat.Return (fs, ast) + local e = {} -- expdesc + local first -- registers with returned values + local nret = #ast + + if nret == 0 then first = 0 + else + --printf("[RETURN] compiling explist") + explist (fs, ast, e) + --printf("[RETURN] explist e=%s", tostringv(e)) + if hasmultret (e.k) then + luaK:setmultret(fs, e) + if e.k == "VCALL" and nret == 1 then + luaP:SET_OPCODE(luaK:getcode(fs, e), "OP_TAILCALL") + assert(luaP:GETARG_A(luaK:getcode(fs, e)) == fs.nactvar) + end + first = fs.nactvar + nret = luaK.LUA_MULTRET -- return all values + elseif nret == 1 then + first = luaK:exp2anyreg(fs, e) + else + --printf("* Return multiple vals in nextreg %i", fs.freereg) + luaK:exp2nextreg(fs, e) -- values must go to the 'stack' + first = fs.nactvar -- return all 'active' values + assert(nret == fs.freereg - first) + end + end + luaK:ret(fs, first, nret) +end +------------------------------------------------------------------------ + +function stat.Local (fs, ast) + local names, values = ast[1], ast[2] or { } + for i = 1, #names do new_localvar (fs, names[i][1], i-1) end + local e = { } + if #values == 0 then e.k = "VVOID" else explist (fs, values, e) end + adjust_assign (fs, #names, #values, e) + adjustlocalvars (fs, #names) +end + +------------------------------------------------------------------------ + +function stat.Localrec (fs, ast) + assert(#ast[1]==1 and #ast[2]==1, "Multiple letrecs not implemented yet") + local ast_var, ast_val, e_var, e_val = ast[1][1], ast[2][1], { }, { } + new_localvar (fs, ast_var[1], 0) + init_exp (e_var, "VLOCAL", fs.freereg) + luaK:reserveregs (fs, 1) + adjustlocalvars (fs, 1) + expr.expr (fs, ast_val, e_val) + luaK:storevar (fs, e_var, e_val) + getlocvar (fs, fs.nactvar-1).startpc = fs.pc +end + +------------------------------------------------------------------------ + +function stat.If (fs, ast) + local astlen = #ast + -- Degenerate case #1: no statement + if astlen==0 then return block(fs, { }) end + -- Degenerate case #2: only an else statement + if astlen==1 then return block(fs, ast[1]) end + + local function test_then_block (fs, test, body) + local condexit = cond (fs, test); + block (fs, body) + return condexit + end + + local escapelist = luaK.NO_JUMP + + local flist = test_then_block (fs, ast[1], ast[2]) -- 'then' statement + for i = 3, #ast - 1, 2 do -- 'elseif' statement + escapelist = luaK:concat( fs, escapelist, luaK:jump(fs)) + luaK:patchtohere (fs, flist) + flist = test_then_block (fs, ast[i], ast[i+1]) + end + if #ast % 2 == 1 then -- 'else' statement + escapelist = luaK:concat(fs, escapelist, luaK:jump(fs)) + luaK:patchtohere(fs, flist) + block (fs, ast[#ast]) + else + escapelist = luaK:concat(fs, escapelist, flist) + end + luaK:patchtohere(fs, escapelist) +end + +------------------------------------------------------------------------ + +function stat.Forin (fs, ast) + local vars, vals, body = ast[1], ast[2], ast[3] + -- imitating forstat: + local bl = { } + enterblock (fs, bl, true) + -- imitating forlist: + local e, base = { }, fs.freereg + new_localvar (fs, "(for generator)", 0) + new_localvar (fs, "(for state)", 1) + new_localvar (fs, "(for control)", 2) + for i = 1, #vars do new_localvar (fs, vars[i][1], i+2) end + explist (fs, vals, e) + adjust_assign (fs, 3, #vals, e) + luaK:checkstack (fs, 3) + forbody (fs, body, base, #vars, false) + -- back to forstat: + leaveblock (fs) +end + +------------------------------------------------------------------------ + +function stat.Fornum (fs, ast) + + local function exp1 (ast_e) + local e = { } + expr.expr (fs, ast_e, e) + luaK:exp2nextreg (fs, e) + end + -- imitating forstat: + local bl = { } + enterblock (fs, bl, true) + -- imitating fornum: + local base = fs.freereg + new_localvar (fs, "(for index)", 0) + new_localvar (fs, "(for limit)", 1) + new_localvar (fs, "(for step)", 2) + new_localvar (fs, ast[1][1], 3) + exp1 (ast[2]) -- initial value + exp1 (ast[3]) -- limit + if #ast == 5 then exp1 (ast[4]) else -- default step = 1 + luaK:codeABx(fs, "OP_LOADK", fs.freereg, luaK:numberK(fs, 1)) + luaK:reserveregs(fs, 1) + end + forbody (fs, ast[#ast], base, 1, true) + -- back to forstat: + leaveblock (fs) +end + +------------------------------------------------------------------------ +function stat.Repeat (fs, ast) + local repeat_init = luaK:getlabel (fs) + local bl1, bl2 = { }, { } + enterblock (fs, bl1, true) + enterblock (fs, bl2, false) + chunk (fs, ast[1]) + local condexit = cond (fs, ast[2]) + if not bl2.upval then + leaveblock (fs) + luaK:patchlist (fs, condexit, repeat_init) + else + stat.Break (fs) + luaK:patchtohere (fs, condexit) + leaveblock (fs) + luaK:patchlist (fs, luaK:jump (fs), repeat_init) + end + leaveblock (fs) +end + +------------------------------------------------------------------------ + +function stat.While (fs, ast) + local whileinit = luaK:getlabel (fs) + local condexit = cond (fs, ast[1]) + local bl = { } + enterblock (fs, bl, true) + block (fs, ast[2]) + luaK:patchlist (fs, luaK:jump (fs), whileinit) + leaveblock (fs) + luaK:patchtohere (fs, condexit); +end + +------------------------------------------------------------------------ + +-- FIXME: it's cumbersome to write this in this semi-recursive way. +function stat.Set (fs, ast) + local ast_lhs, ast_vals, e = ast[1], ast[2], { } + + --print "\n\nSet ast_lhs ast_vals:" + --print(disp.ast(ast_lhs)) + --print(disp.ast(ast_vals)) + + local function let_aux (lhs, nvars) + local legal = { VLOCAL=1, VUPVAL=1, VGLOBAL=1, VINDEXED=1 } + --printv(lhs) + if not legal [lhs.v.k] then + error ("Bad lhs expr: "..pp.tostring(ast_lhs)) + end + if nvars < #ast_lhs then -- this is not the last lhs + local nv = { v = { }, prev = lhs } + expr.expr (fs, ast_lhs [nvars+1], nv.v) + if nv.v.k == "VLOCAL" then check_conflict (fs, lhs, nv.v) end + let_aux (nv, nvars+1) + else -- this IS the last lhs + explist (fs, ast_vals, e) + if #ast_vals < nvars then + adjust_assign (fs, nvars, #ast_vals, e) + elseif #ast_vals > nvars then + adjust_assign (fs, nvars, #ast_vals, e) + fs.freereg = fs.freereg - #ast_vals + nvars + else -- #ast_vals == nvars (and we're at last lhs) + luaK:setoneret (fs, e) -- close last expression + luaK:storevar (fs, lhs.v, e) + return -- avoid default + end + end + init_exp (e, "VNONRELOC", fs.freereg - 1) -- default assignment + luaK:storevar (fs, lhs.v, e) + end + + local lhs = { v = { }, prev = nil } + expr.expr (fs, ast_lhs[1], lhs.v) + let_aux( lhs, 1) +end + +------------------------------------------------------------------------ + +function stat.Call (fs, ast) + local v = { } + expr.Call (fs, ast, v) + luaP:SETARG_C (luaK:getcode(fs, v), 1) +end + +------------------------------------------------------------------------ + +function stat.Invoke (fs, ast) + local v = { } + expr.Invoke (fs, ast, v) + --FIXME: didn't check that, just copied from stat.Call + luaP:SETARG_C (luaK:getcode(fs, v), 1) +end + + +local function patch_goto (fs, src, dst) + +end + + +------------------------------------------------------------------------ +-- Goto/Label data: +-- fs.labels :: string => { nactvar :: int; pc :: int } +-- fs.forward_gotos :: string => list(int) +-- +-- fs.labels goes from label ids to the number of active variables at +-- the label's PC, and that PC +-- +-- fs.forward_gotos goes from label ids to the list of the PC where +-- some goto wants to jump to this label. Since gotos are actually made +-- up of two instructions OP_CLOSE and OP_JMP, it's the first instruction's +-- PC that's stored in fs.forward_gotos +-- +-- Note that backward gotos aren't stored: since their destination is knowns +-- when they're compiled, their target is directly set. +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +-- Set a Label to jump to with Goto +------------------------------------------------------------------------ +function stat.Label (fs, ast) + local label_id = ast[1] + if type(label_id)=='table' then label_id=label_id[1] end + -- printf("Label %s at PC %i", label_id, fs.pc) + ------------------------------------------------------------------- + -- Register the label, so that future gotos can use it. + ------------------------------------------------------------------- + if fs.labels [label_id] then error "Duplicate label in function" + else fs.labels [label_id] = { pc = fs.pc; nactvar = fs.nactvar } end + local gotos = fs.forward_gotos [label_id] + if gotos then + ---------------------------------------------------------------- + -- Patch forward gotos which were targetting this label. + ---------------------------------------------------------------- + for _, goto_pc in ipairs(gotos) do + local close_instr = fs.f.code[goto_pc] + local jmp_instr = fs.f.code[goto_pc+1] + local goto_nactvar = luaP:GETARG_A (close_instr) + if fs.nactvar < goto_nactvar then + luaP:SETARG_A (close_instr, fs.nactvar) end + luaP:SETARG_sBx (jmp_instr, fs.pc - goto_pc - 2) + end + ---------------------------------------------------------------- + -- Gotos are patched, they can be forgotten about (when the + -- function will be finished, it will be checked that all gotos + -- have been patched, by checking that forward_goto is empty). + ---------------------------------------------------------------- + fs.forward_gotos[label_id] = nil + end +end + +------------------------------------------------------------------------ +-- jumps to a label set with stat.Label. +-- Argument must be a String or an Id +-- FIXME/optim: get rid of useless OP_CLOSE when nactvar doesn't change. +-- Thsi must be done both here for backward gotos, and in +-- stat.Label for forward gotos. +------------------------------------------------------------------------ +function stat.Goto (fs, ast) + local label_id = ast[1] + if type(label_id)=='table' then label_id=label_id[1] end + -- printf("Goto %s at PC %i", label_id, fs.pc) + local label = fs.labels[label_id] + if label then + ---------------------------------------------------------------- + -- Backward goto: the label already exists, so I can get its + -- nactvar and address directly. nactvar is used to close + -- upvalues if we get out of scoping blocks by jumping. + ---------------------------------------------------------------- + if fs.nactvar > label.nactvar then + luaK:codeABC (fs, "OP_CLOSE", label.nactvar, 0, 0) end + local offset = label.pc - fs.pc - 1 + luaK:codeAsBx (fs, "OP_JMP", 0, offset) + else + ---------------------------------------------------------------- + -- Forward goto: will be patched when the matching label is + -- found, forward_gotos[label_id] keeps the PC of the CLOSE + -- instruction just before the JMP. [stat.Label] will use it to + -- patch the OP_CLOSE and the OP_JMP. + ---------------------------------------------------------------- + if not fs.forward_gotos[label_id] then + fs.forward_gotos[label_id] = { } end + table.insert (fs.forward_gotos[label_id], fs.pc) + luaK:codeABC (fs, "OP_CLOSE", fs.nactvar, 0, 0) + luaK:codeAsBx (fs, "OP_JMP", 0, luaK.NO_JUMP) + end +end + +------------------------------------------------------------------------ +------------------------------------------------------------------------ +-- +-- Expression parsers table +-- +------------------------------------------------------------------------ +------------------------------------------------------------------------ + +function expr.expr (fs, ast, v) + if type(ast) ~= "table" then + error ("Expr AST expected, got "..pp.tostring(ast)) end + + if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end + + --debugf (" - Expression %s", table.tostring (ast)) + local parser = expr[ast.tag] + if parser then parser (fs, ast, v) + elseif not ast.tag then + error ("No tag in expression ".. + pp.tostring(ast, {line_max=80, hide_hash=1, metalua_tag=1})) + else + error ("No parser for node `"..ast.tag) end + --debugf (" - /Expression `%s", ast.tag) +end + +------------------------------------------------------------------------ + +function expr.Nil (fs, ast, v) init_exp (v, "VNIL", 0) end +function expr.True (fs, ast, v) init_exp (v, "VTRUE", 0) end +function expr.False (fs, ast, v) init_exp (v, "VFALSE", 0) end +function expr.String (fs, ast, v) codestring (fs, v, ast[1]) end +function expr.Number (fs, ast, v) + init_exp (v, "VKNUM", 0) + v.nval = ast[1] +end + +function expr.Paren (fs, ast, v) + expr.expr (fs, ast[1], v) + luaK:setoneret (fs, v) +end + +function expr.Dots (fs, ast, v) + assert (fs.f.is_vararg ~= 0, "No vararg in this function") + -- NEEDSARG flag is set if and only if the function is a vararg, + -- but no vararg has been used yet in its code. + if fs.f.is_vararg < M.VARARG_NEEDSARG then + fs.f.is_varag = fs.f.is_vararg - M.VARARG_NEEDSARG end + init_exp (v, "VVARARG", luaK:codeABC (fs, "OP_VARARG", 0, 1, 0)) +end + +------------------------------------------------------------------------ + +function expr.Table (fs, ast, v) + local pc = luaK:codeABC(fs, "OP_NEWTABLE", 0, 0, 0) + local cc = { v = { } , na = 0, nh = 0, tostore = 0, t = v } -- ConsControl + init_exp (v, "VRELOCABLE", pc) + init_exp (cc.v, "VVOID", 0) -- no value (yet) + luaK:exp2nextreg (fs, v) -- fix it at stack top (for gc) + for i = 1, #ast do + assert(cc.v.k == "VVOID" or cc.tostore > 0) + closelistfield(fs, cc); + (ast[i].tag == "Pair" and recfield or listfield) (fs, ast[i], cc) + end + lastlistfield(fs, cc) + + -- Configure [OP_NEWTABLE] dimensions + luaP:SETARG_B(fs.f.code[pc], int2fb(cc.na)) -- set initial array size + luaP:SETARG_C(fs.f.code[pc], int2fb(cc.nh)) -- set initial table size + --printv(fs.f.code[pc]) +end + + +------------------------------------------------------------------------ + +function expr.Function (fs, ast, v) + if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end + + local new_fs = open_func(fs) + if ast.lineinfo then + new_fs.f.lineDefined, new_fs.f.lastLineDefined = + ast.lineinfo.first.line, ast.lineinfo.last.line + end + parlist (new_fs, ast[1]) + chunk (new_fs, ast[2]) + close_func (new_fs) + pushclosure(fs, new_fs, v) +end + +------------------------------------------------------------------------ + +function expr.Op (fs, ast, v) + if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end + local op = ast[1] + + if #ast == 2 then + expr.expr (fs, ast[2], v) + luaK:prefix (fs, op, v) + elseif #ast == 3 then + local v2 = { } + expr.expr (fs, ast[2], v) + luaK:infix (fs, op, v) + expr.expr (fs, ast[3], v2) + luaK:posfix (fs, op, v, v2) + else + error "Wrong arg number" + end +end + +------------------------------------------------------------------------ + +function expr.Call (fs, ast, v) + expr.expr (fs, ast[1], v) + luaK:exp2nextreg (fs, v) + funcargs(fs, ast, v, 2) + --debugf("after expr.Call: %s, %s", v.k, luaP.opnames[luaK:getcode(fs, v).OP]) +end + +------------------------------------------------------------------------ +-- `Invoke{ table key args } +function expr.Invoke (fs, ast, v) + expr.expr (fs, ast[1], v) + luaK:dischargevars (fs, v) + local key = { } + codestring (fs, key, ast[2][1]) + luaK:_self (fs, v, key) + funcargs (fs, ast, v, 3) +end + +------------------------------------------------------------------------ + +function expr.Index (fs, ast, v) + if #ast ~= 2 then + print"\n\nBAD INDEX AST:" + pp.print(ast) + error "generalized indexes not implemented" end + + if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end + + --assert(fs.lastline ~= 0, ast.tag) + + expr.expr (fs, ast[1], v) + luaK:exp2anyreg (fs, v) + + local k = { } + expr.expr (fs, ast[2], k) + luaK:exp2val (fs, k) + luaK:indexed (fs, v, k) +end + +------------------------------------------------------------------------ + +function expr.Id (fs, ast, v) + assert (ast.tag == "Id") + singlevar (fs, ast[1], v) +end + +------------------------------------------------------------------------ + +function expr.Stat (fs, ast, v) + --printf(" * Stat: %i actvars, first freereg is %i", fs.nactvar, fs.freereg) + --printf(" actvars: %s", table.tostring(fs.actvar)) + + -- Protect temporary stack values by pretending they are local + -- variables. Local vars are in registers 0 ... fs.nactvar-1, + -- and temporary unnamed variables in fs.nactvar ... fs.freereg-1 + local save_nactvar = fs.nactvar + + -- Eventually, the result should go on top of stack *after all + -- `Stat{ } related computation and string usage is over. The index + -- of this destination register is kept here: + local dest_reg = fs.freereg + + -- There might be variables in actvar whose register is > nactvar, + -- and therefore will not be protected by the "nactvar := freereg" + -- trick. Indeed, `Local only increases nactvar after the variable + -- content has been computed. Therefore, in + -- "local foo = -{`Stat{...}}", variable foo will be messed up by + -- the compilation of `Stat. + -- FIX: save the active variables at indices >= nactvar in + -- save_actvar, and restore them after `Stat has been computed. + -- + -- I use a while rather than for loops and length operators because + -- fs.actvar is a 0-based array... + local save_actvar = { } do + local i = fs.nactvar + while true do + local v = fs.actvar[i] + if not v then break end + --printf("save hald-baked actvar %s at index %i", table.tostring(v), i) + save_actvar[i] = v + i=i+1 + end + end + + fs.nactvar = fs.freereg -- Now temp unnamed registers are protected + enterblock (fs, { }, false) + chunk (fs, ast[1]) + expr.expr (fs, ast[2], v) + luaK:exp2nextreg (fs, v) + leaveblock (fs) + luaK:exp2reg (fs, v, dest_reg) + + -- Reserve the newly allocated stack level + -- Puzzled note: here was written "fs.freereg = fs.freereg+1". + -- I'm pretty sure it should rather be dest_reg+1, but maybe + -- both are equivalent? + fs.freereg = dest_reg+1 + + -- Restore nactvar, so that intermediate stacked value stop + -- being protected. + --printf(" nactvar back from %i to %i", fs.nactvar, save_nactvar) + fs.nactvar = save_nactvar + + -- restore messed-up unregistered local vars + for i, j in pairs(save_actvar) do + --printf(" Restoring actvar %i", i) + fs.actvar[i] = j + end + --printf(" * End of Stat") +end + +------------------------------------------------------------------------ +-- Main function: ast --> proto +------------------------------------------------------------------------ +function M.ast_to_proto (ast, source) + local fs = open_func (nil) + fs.f.is_vararg = M.VARARG_ISVARARG + chunk (fs, ast) + close_func (fs) + assert (fs.prev == nil) + assert (fs.f.nups == 0) + assert (fs.nestlevel == 0) + if source then fs.f.source = source end + return fs.f, source +end + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lcode.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lcode.lua new file mode 100644 index 000000000..ede1a1c45 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lcode.lua @@ -0,0 +1,1038 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2005-2013 Kein-Hong Man, Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang +-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua +-- +------------------------------------------------------------------------------- + +--[[-------------------------------------------------------------------- + + $Id$ + + lcode.lua + Lua 5 code generator in Lua + This file is part of Yueliang. + + Copyright (c) 2005 Kein-Hong Man + The COPYRIGHT file describes the conditions + under which this software may be distributed. + + See the ChangeLog for more information. + +------------------------------------------------------------------------ + + [FF] Slightly modified, mainly to produce Lua 5.1 bytecode. + +----------------------------------------------------------------------]] + +--[[-------------------------------------------------------------------- +-- Notes: +-- * one function manipulate a pointer argument with a simple data type +-- (can't be emulated by a table, ambiguous), now returns that value: +-- luaK:concat(fs, l1, l2) +-- * some function parameters changed to boolean, additional code +-- translates boolean back to 1/0 for instruction fields +-- * Added: +-- luaK:ttisnumber(o) (from lobject.h) +-- luaK:nvalue(o) (from lobject.h) +-- luaK:setnilvalue(o) (from lobject.h) +-- luaK:setsvalue(o) (from lobject.h) +-- luaK:setnvalue(o) (from lobject.h) +-- luaK:sethvalue(o) (from lobject.h) +----------------------------------------------------------------------]] + +local luaP = require 'metalua.compiler.bytecode.lopcodes' + +local function debugf() end + +local luaK = { } + +luaK.MAXSTACK = 250 -- (llimits.h, used in lcode.lua) +luaK.LUA_MULTRET = -1 -- (lua.h) + +------------------------------------------------------------------------ +-- Marks the end of a patch list. It is an invalid value both as an absolute +-- address, and as a list link (would link an element to itself). +------------------------------------------------------------------------ +luaK.NO_JUMP = -1 + +--FF 5.1 +function luaK:isnumeral(e) + return e.k=="VKNUM" and e.t==self.NO_JUMP and e.t==self.NO_JUMP +end + +------------------------------------------------------------------------ +-- emulation of TObject macros (these are from lobject.h) +-- * TObject is a table since lcode passes references around +-- * tt member field removed, using Lua's type() instead +------------------------------------------------------------------------ +function luaK:ttisnumber(o) + if o then return type(o.value) == "number" else return false end +end +function luaK:nvalue(o) return o.value end +function luaK:setnilvalue(o) o.value = nil end +function luaK:setsvalue(o, s) o.value = s end +luaK.setnvalue = luaK.setsvalue +luaK.sethvalue = luaK.setsvalue + +------------------------------------------------------------------------ +-- returns the instruction object for given e (expdesc) +------------------------------------------------------------------------ +function luaK:getcode(fs, e) + return fs.f.code[e.info] +end + +------------------------------------------------------------------------ +-- codes an instruction with a signed Bx (sBx) field +------------------------------------------------------------------------ +function luaK:codeAsBx(fs, o, A, sBx) + return self:codeABx(fs, o, A, sBx + luaP.MAXARG_sBx) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:hasjumps(e) + return e.t ~= e.f +end + +------------------------------------------------------------------------ +-- FF updated 5.1 +------------------------------------------------------------------------ +function luaK:_nil(fs, from, n) + if fs.pc > fs.lasttarget then -- no jumps to current position? + if fs.pc == 0 then return end --function start, positions are already clean + local previous = fs.f.code[fs.pc - 1] + if luaP:GET_OPCODE(previous) == "OP_LOADNIL" then + local pfrom = luaP:GETARG_A(previous) + local pto = luaP:GETARG_B(previous) + if pfrom <= from and from <= pto + 1 then -- can connect both? + if from + n - 1 > pto then + luaP:SETARG_B(previous, from + n - 1) + end + return + end + end + end + self:codeABC(fs, "OP_LOADNIL", from, from + n - 1, 0) -- else no optimization +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:jump(fs) + local jpc = fs.jpc -- save list of jumps to here + fs.jpc = self.NO_JUMP + local j = self:codeAsBx(fs, "OP_JMP", 0, self.NO_JUMP) + return self:concat(fs, j, jpc) -- keep them on hold +end + +--FF 5.1 +function luaK:ret (fs, first, nret) + luaK:codeABC (fs, "OP_RETURN", first, nret+1, 0) +end + + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:condjump(fs, op, A, B, C) + self:codeABC(fs, op, A, B, C) + return self:jump(fs) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:fixjump(fs, pc, dest) + local jmp = fs.f.code[pc] + local offset = dest - (pc + 1) + assert(dest ~= self.NO_JUMP) + if math.abs(offset) > luaP.MAXARG_sBx then + error("control structure too long") + end + luaP:SETARG_sBx(jmp, offset) +end + +------------------------------------------------------------------------ +-- returns current 'pc' and marks it as a jump target (to avoid wrong +-- optimizations with consecutive instructions not in the same basic block). +------------------------------------------------------------------------ +function luaK:getlabel(fs) + fs.lasttarget = fs.pc + return fs.pc +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:getjump(fs, pc) + local offset = luaP:GETARG_sBx(fs.f.code[pc]) + if offset == self.NO_JUMP then -- point to itself represents end of list + return self.NO_JUMP -- end of list + else + return (pc + 1) + offset -- turn offset into absolute position + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:getjumpcontrol(fs, pc) + local pi = fs.f.code[pc] + local ppi = fs.f.code[pc - 1] + if pc >= 1 and luaP:testOpMode(luaP:GET_OPCODE(ppi), "OpModeT") then + return ppi + else + return pi + end +end + +------------------------------------------------------------------------ +-- check whether list has any jump that do not produce a value +-- (or produce an inverted value) +------------------------------------------------------------------------ +--FF updated 5.1 +function luaK:need_value(fs, list, cond) + while list ~= self.NO_JUMP do + local i = self:getjumpcontrol(fs, list) + if luaP:GET_OPCODE(i) ~= "OP_TESTSET" or + luaP:GETARG_A(i) ~= luaP.NO_REG or + luaP:GETARG_C(i) ~= cond then + return true + end + list = self:getjump(fs, list) + end + return false -- not found +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +--FF updated 5.1 +function luaK:patchtestreg(fs, node, reg) + assert(reg) -- pour assurer, vu que j'ai ajoute un parametre p/r a 5.0 + local i = self:getjumpcontrol(fs, node) + if luaP:GET_OPCODE(i) ~= "OP_TESTSET" then + return false end -- cannot patch other instructions + if reg ~= luaP.NO_REG and reg ~= luaP:GETARG_B(i) then + luaP:SETARG_A(i, reg) + else + -- no register to put value or register already has the value + luaP:SET_OPCODE(i, "OP_TEST") + luaP:SETARG_A(i, luaP:GETARG_B(i)) + luaP:SETARG_B(i, 0) + luaP:SETARG_C(i, luaP:GETARG_C(i)) + end + return true +end + +--FF added 5.1 +function luaK:removevalues (fs, list) + while list ~= self.NO_JUMP do + self:patchtestreg (fs, list, luaP.NO_REG) + list = self:getjump (fs, list) + end +end + +------------------------------------------------------------------------ +-- FF updated 5.1 +------------------------------------------------------------------------ +function luaK:patchlistaux(fs, list, vtarget, reg, dtarget) + while list ~= self.NO_JUMP do + local _next = self:getjump(fs, list) + if self:patchtestreg (fs, list, reg) then + self:fixjump(fs, list, vtarget) + else + self:fixjump (fs, list, dtarget) + end + list = _next + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:dischargejpc(fs) + self:patchlistaux(fs, fs.jpc, fs.pc, luaP.NO_REG, fs.pc) + fs.jpc = self.NO_JUMP +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:patchlist(fs, list, target) + if target == fs.pc then + self:patchtohere(fs, list) + else + assert(target < fs.pc) + self:patchlistaux(fs, list, target, luaP.NO_REG, target) + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:patchtohere(fs, list) + self:getlabel(fs) + fs.jpc = self:concat(fs, fs.jpc, list) +end + +------------------------------------------------------------------------ +-- * l1 was a pointer, now l1 is returned and callee assigns the value +------------------------------------------------------------------------ +function luaK:concat(fs, l1, l2) + if l2 == self.NO_JUMP then return l1 -- unchanged + elseif l1 == self.NO_JUMP then + return l2 -- changed + else + local list = l1 + local _next = self:getjump(fs, list) + while _next ~= self.NO_JUMP do -- find last element + list = _next + _next = self:getjump(fs, list) + end + self:fixjump(fs, list, l2) + end + return l1 -- unchanged +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:checkstack(fs, n) + local newstack = fs.freereg + n + if newstack > fs.f.maxstacksize then + if newstack >= luaK.MAXSTACK then + error("function or expression too complex") + end + fs.f.maxstacksize = newstack + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:reserveregs(fs, n) + self:checkstack(fs, n) + fs.freereg = fs.freereg + n +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:freereg(fs, reg) + if not luaP:ISK (reg) and reg >= fs.nactvar then + fs.freereg = fs.freereg - 1 + assert(reg == fs.freereg, + string.format("reg=%i, fs.freereg=%i", reg, fs.freereg)) + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:freeexp(fs, e) + if e.k == "VNONRELOC" then + self:freereg(fs, e.info) + end +end + +------------------------------------------------------------------------ +-- k is a constant, v is... what? +-- fs.h is a hash value --> index in f.k +------------------------------------------------------------------------ +-- * luaH_get, luaH_set deleted; direct table access used instead +-- * luaO_rawequalObj deleted in first assert +-- * setobj2n deleted in assignment of v to f.k table +------------------------------------------------------------------------ +--FF radically updated, not completely understood +function luaK:addk(fs, k, v) + local idx = fs.h[k.value] + local f = fs.f +-- local oldsize = f.sizek + if self:ttisnumber (idx) then + --TODO this assert currently FAILS + --assert(fs.f.k[self:nvalue(idx)] == v) + return self:nvalue(idx) + else -- constant not found; create a new entry + do + local t = type (v.value) + assert(t=="nil" or t=="string" or t=="number" or t=="boolean") + end + --debugf("[const: k[%i] = %s ]", fs.nk, tostringv(v.value)) + fs.f.k[fs.nk] = v + fs.h[k.value] = { } + self:setnvalue(fs.h[k.value], fs.nk) + local nk = fs.nk + fs.nk = fs.nk+1 + return nk + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:stringK(fs, s) + assert (type(s)=="string") + local o = {} -- TObject + self:setsvalue(o, s) + return self:addk(fs, o, o) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:numberK(fs, r) + assert (type(r)=="number") + local o = {} -- TObject + self:setnvalue(o, r) + return self:addk(fs, o, o) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:boolK(fs, r) + assert (type(r)=="boolean") + local o = {} -- TObject + self:setnvalue(o, r) + return self:addk(fs, o, o) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:nilK(fs) + local k, v = {}, {} -- TObject + self:setnilvalue(v) + self:sethvalue(k, fs.h) -- cannot use nil as key; instead use table itself + return self:addk(fs, k, v) +end + + +--FF 5.1 +function luaK:setreturns (fs, e, nresults) + if e.k == "VCALL" then -- expression is an open function call? + luaP:SETARG_C(self:getcode(fs, e), nresults + 1) + elseif e.k == "VVARARG" then + luaP:SETARG_B (self:getcode (fs, e), nresults + 1) + luaP:SETARG_A (self:getcode (fs, e), fs.freereg) + self:reserveregs (fs, 1) + end +end + +--FF 5.1 +function luaK:setmultret (fs, e) + self:setreturns (fs, e, self.LUA_MULTRET) +end + +--FF 5.1 +function luaK:setoneret (fs, e) + if e.k == "VCALL" then -- expression is an open function call? + e.k = "VNONRELOC" + e.info = luaP:GETARG_A(self:getcode(fs, e)) + elseif e.k == "VVARARG" then + luaP:SETARG_B (self:getcode (fs, e), 2) + e.k = "VRELOCABLE" + end +end + + +------------------------------------------------------------------------ +--FF deprecated in 5.1 +------------------------------------------------------------------------ +function luaK:setcallreturns(fs, e, nresults) + assert (false, "setcallreturns deprecated") + --print "SCR:" + --printv(e) + --printv(self:getcode(fs, e)) + if e.k == "VCALL" then -- expression is an open function call? + luaP:SETARG_C(self:getcode(fs, e), nresults + 1) + if nresults == 1 then -- 'regular' expression? + e.k = "VNONRELOC" + e.info = luaP:GETARG_A(self:getcode(fs, e)) + end + elseif e.k == "VVARARG" then + --printf("Handle vararg return on expr %s, whose code is %s", + -- tostringv(e), tostringv(self:getcode(fs, e))) + if nresults == 1 then + luaP:SETARG_B (self:getcode (fs, e), 2) + e.k = "VRELOCABLE" +--FIXME: why no SETARG_A??? + else + luaP:SETARG_B (self:getcode (fs, e), nresults + 1) + luaP:SETARG_A (self:getcode (fs, e), fs.freereg) + self:reserveregs (fs, 1) + --printf("Now code is %s", tostringv(self:getcode(fs, e))) + end + end +end + +------------------------------------------------------------------------ +-- Ajoute le code pour effectuer l'extraction de la locvar/upval/globvar +-- /idx, sachant +------------------------------------------------------------------------ +function luaK:dischargevars(fs, e) +--printf("\ndischargevars\n") + local k = e.k + if k == "VLOCAL" then + e.k = "VNONRELOC" + elseif k == "VUPVAL" then + e.info = self:codeABC(fs, "OP_GETUPVAL", 0, e.info, 0) + e.k = "VRELOCABLE" + elseif k == "VGLOBAL" then + e.info = self:codeABx(fs, "OP_GETGLOBAL", 0, e.info) + e.k = "VRELOCABLE" + elseif k == "VINDEXED" then + self:freereg(fs, e.aux) + self:freereg(fs, e.info) + e.info = self:codeABC(fs, "OP_GETTABLE", 0, e.info, e.aux) + e.k = "VRELOCABLE" + elseif k == "VCALL" or k == "VVARARG" then + self:setoneret(fs, e) + else + -- there is one value available (somewhere) + end +--printf("\n/dischargevars\n") +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:code_label(fs, A, b, jump) + self:getlabel(fs) -- those instructions may be jump targets + return self:codeABC(fs, "OP_LOADBOOL", A, b, jump) +end + +------------------------------------------------------------------------ +-- FF updated 5.1 +------------------------------------------------------------------------ +function luaK:discharge2reg(fs, e, reg) + self:dischargevars(fs, e) + local k = e.k + if k == "VNIL" then + self:_nil(fs, reg, 1) + elseif k == "VFALSE" or k == "VTRUE" then + self:codeABC(fs, "OP_LOADBOOL", reg, (e.k == "VTRUE") and 1 or 0, 0) + elseif k == "VKNUM" then + self:codeABx (fs, "OP_LOADK", reg, self:numberK(fs, e.nval)) + elseif k == "VK" then + self:codeABx(fs, "OP_LOADK", reg, e.info) + elseif k == "VRELOCABLE" then + local pc = self:getcode(fs, e) + luaP:SETARG_A(pc, reg) + elseif k == "VNONRELOC" then + if reg ~= e.info then + self:codeABC(fs, "OP_MOVE", reg, e.info, 0) + end + else + assert(e.k == "VVOID" or e.k == "VJMP") + return -- nothing to do... + end + e.info = reg + e.k = "VNONRELOC" +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:discharge2anyreg(fs, e) + if e.k ~= "VNONRELOC" then + self:reserveregs(fs, 1) + self:discharge2reg(fs, e, fs.freereg - 1) + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:exp2reg(fs, e, reg) + self:discharge2reg(fs, e, reg) + if e.k == "VJMP" then + e.t = self:concat(fs, e.t, e.info) -- put this jump in 't' list + end + if self:hasjumps(e) then + local final -- position after whole expression + local p_f = self.NO_JUMP -- position of an eventual LOAD false + local p_t = self.NO_JUMP -- position of an eventual LOAD true + if self:need_value(fs, e.t, 1) or self:need_value(fs, e.f, 0) then + local fj = self.NO_JUMP -- first jump (over LOAD ops.) + if e.k ~= "VJMP" then fj = self:jump(fs) end + p_f = self:code_label(fs, reg, 0, 1) + p_t = self:code_label(fs, reg, 1, 0) + self:patchtohere(fs, fj) + end + final = self:getlabel(fs) + self:patchlistaux(fs, e.f, final, reg, p_f) + self:patchlistaux(fs, e.t, final, reg, p_t) + end + e.f, e.t = self.NO_JUMP, self.NO_JUMP + e.info = reg + e.k = "VNONRELOC" +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:exp2nextreg(fs, e) + self:dischargevars(fs, e) + --[FF] Allready in place (added for expr.Stat) + if e.k == "VNONRELOC" and e.info == fs.freereg then + --printf("Expression already in next reg %i: %s", fs.freereg, tostringv(e)) + return end + self:freeexp(fs, e) + self:reserveregs(fs, 1) + self:exp2reg(fs, e, fs.freereg - 1) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:exp2anyreg(fs, e) + --printf("exp2anyregs(e=%s)", tostringv(e)) + self:dischargevars(fs, e) + if e.k == "VNONRELOC" then + if not self:hasjumps(e) then -- exp is already in a register + return e.info + end + if e.info >= fs.nactvar then -- reg. is not a local? + self:exp2reg(fs, e, e.info) -- put value on it + return e.info + end + end + self:exp2nextreg(fs, e) -- default + return e.info +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:exp2val(fs, e) + if self:hasjumps(e) then + self:exp2anyreg(fs, e) + else + self:dischargevars(fs, e) + end +end + +------------------------------------------------------------------------ +-- FF updated 5.1 +------------------------------------------------------------------------ +function luaK:exp2RK(fs, e) + self:exp2val(fs, e) + local k = e.k + if k=="VNIL" or k=="VTRUE" or k=="VFALSE" or k=="VKNUM" then + if fs.nk <= luaP.MAXINDEXRK then + if k=="VNIL" then e.info = self:nilK(fs) + elseif k=="VKNUM" then e.info = self:numberK (fs, e.nval) + else e.info = self:boolK(fs, e.k=="VTRUE") end + e.k = "VK" + return luaP:RKASK(e.info) + end + elseif k == "VK" then + if e.info <= luaP.MAXINDEXRK then -- constant fit in argC? + return luaP:RKASK (e.info) + end + end + -- not a constant in the right range: put it in a register + return self:exp2anyreg(fs, e) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:storevar(fs, var, exp) + --print("STOREVAR") + --printf("var=%s", tostringv(var)) + --printf("exp=%s", tostringv(exp)) + + local k = var.k + if k == "VLOCAL" then + self:freeexp(fs, exp) + self:exp2reg(fs, exp, var.info) + return + elseif k == "VUPVAL" then + local e = self:exp2anyreg(fs, exp) + self:codeABC(fs, "OP_SETUPVAL", e, var.info, 0) + elseif k == "VGLOBAL" then + --printf("store global, exp=%s", tostringv(exp)) + local e = self:exp2anyreg(fs, exp) + self:codeABx(fs, "OP_SETGLOBAL", e, var.info) + elseif k == "VINDEXED" then + local e = self:exp2RK(fs, exp) + self:codeABC(fs, "OP_SETTABLE", var.info, var.aux, e) + else + assert(0) -- invalid var kind to store + end + self:freeexp(fs, exp) + --print("/STOREVAR") +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:_self(fs, e, key) + self:exp2anyreg(fs, e) + self:freeexp(fs, e) + local func = fs.freereg + self:reserveregs(fs, 2) + self:codeABC(fs, "OP_SELF", func, e.info, self:exp2RK(fs, key)) + self:freeexp(fs, key) + e.info = func + e.k = "VNONRELOC" +end + +------------------------------------------------------------------------ +-- FF updated 5.1 +------------------------------------------------------------------------ +function luaK:invertjump(fs, e) + --printf("invertjump on jump instruction #%i", e.info) + --printv(self:getcode(fs, e)) + local pc = self:getjumpcontrol(fs, e.info) + assert(luaP:testOpMode(luaP:GET_OPCODE(pc), "OpModeT") and + luaP:GET_OPCODE(pc) ~= "OP_TESTSET" and + luaP:GET_OPCODE(pc) ~= "OP_TEST") + --printf("Before invert:") + --printv(pc) + luaP:SETARG_A(pc, (luaP:GETARG_A(pc) == 0) and 1 or 0) + --printf("After invert:") + --printv(pc) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:jumponcond(fs, e, cond) + if e.k == "VRELOCABLE" then + local ie = self:getcode(fs, e) + if luaP:GET_OPCODE(ie) == "OP_NOT" then + fs.pc = fs.pc - 1 -- remove previous OP_NOT + return self:condjump(fs, "OP_TEST", luaP:GETARG_B(ie), 0, + cond and 0 or 1) + end + -- else go through + end + self:discharge2anyreg(fs, e) + self:freeexp(fs, e) + return self:condjump(fs, "OP_TESTSET", luaP.NO_REG, e.info, cond and 1 or 0) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:goiftrue(fs, e) + local pc -- pc of last jump + self:dischargevars(fs, e) + local k = e.k + if k == "VK" or k == "VTRUE" or k == "VKNUM" then + pc = self.NO_JUMP -- always true; do nothing + elseif k == "VFALSE" then + pc = self:jump(fs) -- always jump + elseif k == "VJMP" then + self:invertjump(fs, e) + pc = e.info + else + pc = self:jumponcond(fs, e, false) + end + e.f = self:concat(fs, e.f, pc) -- insert last jump in 'f' list + self:patchtohere(fs, e.t) + e.t = self.NO_JUMP +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:goiffalse(fs, e) + local pc -- pc of last jump + self:dischargevars(fs, e) + local k = e.k + if k == "VNIL" or k == "VFALSE"then + pc = self.NO_JUMP -- always false; do nothing + elseif k == "VTRUE" then + pc = self:jump(fs) -- always jump + elseif k == "VJMP" then + pc = e.info + else + pc = self:jumponcond(fs, e, true) + end + e.t = self:concat(fs, e.t, pc) -- insert last jump in 't' list + self:patchtohere(fs, e.f) + e.f = self.NO_JUMP +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:codenot(fs, e) + self:dischargevars(fs, e) + local k = e.k + if k == "VNIL" or k == "VFALSE" then + e.k = "VTRUE" + elseif k == "VK" or k == "VKNUM" or k == "VTRUE" then + e.k = "VFALSE" + elseif k == "VJMP" then + self:invertjump(fs, e) + elseif k == "VRELOCABLE" or k == "VNONRELOC" then + self:discharge2anyreg(fs, e) + self:freeexp(fs, e) + e.info = self:codeABC(fs, "OP_NOT", 0, e.info, 0) + e.k = "VRELOCABLE" + else + assert(0) -- cannot happen + end + -- interchange true and false lists + e.f, e.t = e.t, e.f + self:removevalues(fs, e.f) + self:removevalues(fs, e.t) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:indexed(fs, t, k) + t.aux = self:exp2RK(fs, k) + t.k = "VINDEXED" +end + +--FF 5.1 +function luaK:constfolding (op, e1, e2) + if not self:isnumeral(e1) or not self:isnumeral(e2) then return false end + local v1, v2, e, r = e1.nval, e2 and e2.nval, nil + if op == "OP_ADD" then r = v1+v2 + elseif op == "OP_SUB" then r = v1-v2 + elseif op == "OP_MUL" then r = v1*v2 + elseif op == "OP_DIV" then if v2==0 then return false end r = v1/v2 + elseif op == "OP_MOD" then if v2==0 then return false end r = v1%v2 + elseif op == "OP_POW" then r = v1^v2 + elseif op == "OP_UNM" then r = -v1 + elseif op == "OP_LEN" then return false + else assert (false, "Unknown numeric value") end + e1.nval = r + return true +end + +--FF 5.1 +function luaK:codearith (fs, op, e1, e2) + if self:constfolding (op, e1, e2) then return else + local o1 = self:exp2RK (fs, e1) + local o2 = 0 + if op ~= "OP_UNM" and op ~= "OP_LEN" then + o2 = self:exp2RK (fs, e2) end + self:freeexp(fs, e2) + self:freeexp(fs, e1) + e1.info = self:codeABC (fs, op, 0, o1, o2) + e1.k = "VRELOCABLE" + end +end + +--FF 5.1 +function luaK:codecomp (fs, op, cond, e1, e2) + assert (type (cond) == "boolean") + local o1 = self:exp2RK (fs, e1) + local o2 = self:exp2RK (fs, e2) + self:freeexp (fs, e2) + self:freeexp (fs, e1) + if not cond and op ~= "OP_EQ" then + local temp = o1; o1=o2; o2=temp cond = true end + e1.info = self:condjump (fs, op, cond and 1 or 0, o1, o2) + e1.k = "VJMP" +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:prefix (fs, op, e) + local e2 = { t = self.NO_JUMP; f = self.NO_JUMP; + k = "VKNUM"; nval = 0 } + if op == "unm" then + if e.k == "VK" then + self:exp2anyreg (fs, e) end + self:codearith (fs, "OP_UNM", e, e2) + elseif op == "not" then + self:codenot (fs, e) + elseif op == "len" then + self:exp2anyreg (fs, e) + self:codearith (fs, "OP_LEN", e, e2) + else + assert (false, "Unknown unary operator") + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:infix (fs, op, v) + if op == "and" then + self:goiftrue(fs, v) + elseif op == "or" then + self:goiffalse(fs, v) + elseif op == "concat" then + self:exp2nextreg(fs, v) -- operand must be on the 'stack' + else + if not self:isnumeral (v) then self:exp2RK(fs, v) end + end +end + +------------------------------------------------------------------------ +-- +-- grep "ORDER OPR" if you change these enums +------------------------------------------------------------------------ +luaK.arith_opc = { -- done as a table lookup instead of a calc + add = "OP_ADD", + sub = "OP_SUB", + mul = "OP_MUL", + mod = "OP_MOD", + div = "OP_DIV", + pow = "OP_POW", + len = "OP_LEN", + ["not"] = "OP_NOT" +} +luaK.test_opc = { -- was ops[] in the codebinop function + eq = {opc="OP_EQ", cond=true}, + lt = {opc="OP_LT", cond=true}, + le = {opc="OP_LE", cond=true}, + + -- Pseudo-ops, with no metatable equivalent: + ne = {opc="OP_EQ", cond=false}, + gt = {opc="OP_LT", cond=false}, + ge = {opc="OP_LE", cond=false} +} + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:posfix(fs, op, e1, e2) + if op == "and" then + assert(e1.t == self.NO_JUMP) -- list must be closed + self:dischargevars(fs, e2) + e2.f = self:concat(fs, e2.f, e1.f) + for k,v in pairs(e2) do e1[k]=v end -- *e1 = *e2 + elseif op == "or" then + assert(e1.f == self.NO_JUMP) -- list must be closed + self:dischargevars(fs, e2) + e2.t = self:concat(fs, e2.t, e1.t) + for k,v in pairs(e2) do e1[k]=v end -- *e1 = *e2 + elseif op == "concat" then + self:exp2val(fs, e2) + if e2.k == "VRELOCABLE" + and luaP:GET_OPCODE(self:getcode(fs, e2)) == "OP_CONCAT" then + assert(e1.info == luaP:GETARG_B(self:getcode(fs, e2)) - 1) + self:freeexp(fs, e1) + luaP:SETARG_B(self:getcode(fs, e2), e1.info) + e1.k = "VRELOCABLE"; e1.info = e2.info + else + self:exp2nextreg(fs, e2) + self:codearith (fs, "OP_CONCAT", e1, e2) + end + else + local opc = self.arith_opc[op] + if opc then self:codearith (fs, opc, e1, e2) else + opc = self.test_opc[op] or error ("Unknown operator "..op) + self:codecomp (fs, opc.opc, opc.cond, e1, e2) + end + end +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:fixline(fs, line) + --assert (line) + if not line then + --print(debug.traceback "fixline (line == nil)") + end + fs.f.lineinfo[fs.pc - 1] = line or 0 +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:code(fs, i, line) + if not line then + line = 0 + --print(debug.traceback "line == nil") + end + local f = fs.f + + do -- print it + local params = { } + for _,x in ipairs{"A","B","Bx", "sBx", "C"} do + if i[x] then table.insert (params, string.format ("%s=%i", x, i[x])) end + end + debugf ("[code:\t%s\t%s]", luaP.opnames[i.OP], table.concat (params, ", ")) + end + + self:dischargejpc(fs) -- 'pc' will change + + f.code[fs.pc] = i + f.lineinfo[fs.pc] = line + + if line == 0 then + f.lineinfo[fs.pc] = fs.lastline + if fs.lastline == 0 then + --print(debug.traceback()) + end + end + + if f.lineinfo[fs.pc] == 0 then + f.lineinfo[fs.pc] = 42 + end + + local pc = fs.pc + fs.pc = fs.pc + 1 + return pc +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:codeABC(fs, o, a, b, c) + assert(luaP:getOpMode(o) == "iABC", o.." is not an ABC operation") + --assert getbmode(o) ~= opargn or b == 0 + --assert getcmode(o) ~= opargn or c == 0 + --FF + --return self:code(fs, luaP:CREATE_ABC(o, a, b, c), fs.ls.lastline) + return self:code(fs, luaP:CREATE_ABC(o, a, b, c), fs.lastline) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:codeABx(fs, o, a, bc) + assert(luaP:getOpMode(o) == "iABx" or luaP:getOpMode(o) == "iAsBx") + --assert getcmode(o) == opargn + --FF + --return self:code(fs, luaP:CREATE_ABx(o, a, bc), fs.ls.lastline) + return self:code(fs, luaP:CREATE_ABx(o, a, bc), fs.lastline) +end + +------------------------------------------------------------------------ +-- +------------------------------------------------------------------------ +function luaK:setlist (fs, base, nelems, tostore) + local c = math.floor ((nelems-1) / luaP.LFIELDS_PER_FLUSH + 1) + local b = tostore == self.LUA_MULTRET and 0 or tostore + assert (tostore ~= 0) + if c <= luaP.MAXARG_C then self:codeABC (fs, "OP_SETLIST", base, b, c) + else + self:codeABC (fs, "OP_SETLIST", base, b, 0) + self:code (fs, c, fs.lastline)--FIXME + end + fs.freereg = base + 1 +end + +return luaK \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/ldump.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/ldump.lua new file mode 100644 index 000000000..6ac76179f --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/ldump.lua @@ -0,0 +1,448 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2005-2013 Kein-Hong Man, Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang +-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua +-- +------------------------------------------------------------------------------- + +--[[-------------------------------------------------------------------- + + ldump.lua + Save bytecodes in Lua + This file is part of Yueliang. + + Copyright (c) 2005 Kein-Hong Man + The COPYRIGHT file describes the conditions + under which this software may be distributed. + +------------------------------------------------------------------------ + + [FF] Slightly modified, mainly to produce Lua 5.1 bytecode. + +----------------------------------------------------------------------]] + +--[[-------------------------------------------------------------------- +-- Notes: +-- * LUA_NUMBER (double), byte order (little endian) and some other +-- header values hard-coded; see other notes below... +-- * One significant difference is that instructions are still in table +-- form (with OP/A/B/C/Bx fields) and luaP:Instruction() is needed to +-- convert them into 4-char strings +-- * Deleted: +-- luaU:DumpVector: folded into DumpLines, DumpCode +-- * Added: +-- luaU:endianness() (from lundump.c) +-- luaU:make_setS: create a chunk writer that writes to a string +-- luaU:make_setF: create a chunk writer that writes to a file +-- (lua.h contains a typedef for a Chunkwriter pointer, and +-- a Lua-based implementation exists, writer() in lstrlib.c) +-- luaU:from_double(x): encode double value for writing +-- luaU:from_int(x): encode integer value for writing +-- (error checking is limited for these conversion functions) +-- (double conversion does not support denormals or NaNs) +-- luaU:ttype(o) (from lobject.h) +----------------------------------------------------------------------]] + +local luaP = require 'metalua.compiler.bytecode.lopcodes' + +local M = { } + +local format = { } +format.header = string.dump(function()end):sub(1, 12) +format.little_endian, format.int_size, +format.size_t_size, format.instr_size, +format.number_size, format.integral = format.header:byte(7, 12) +format.little_endian = format.little_endian~=0 +format.integral = format.integral ~=0 + +assert(format.integral or format.number_size==8, "Number format not supported by dumper") +assert(format.little_endian, "Big endian architectures not supported by dumper") + +--requires luaP +local luaU = { } +M.luaU = luaU + +luaU.format = format + +-- constants used by dumper +luaU.LUA_TNIL = 0 +luaU.LUA_TBOOLEAN = 1 +luaU.LUA_TNUMBER = 3 -- (all in lua.h) +luaU.LUA_TSTRING = 4 +luaU.LUA_TNONE = -1 + +-- definitions for headers of binary files +--luaU.LUA_SIGNATURE = "\27Lua" -- binary files start with "Lua" +--luaU.VERSION = 81 -- 0x50; last format change was in 5.0 +--luaU.FORMAT_VERSION = 0 -- 0 is official version. yeah I know I'm a liar. + +-- a multiple of PI for testing native format +-- multiplying by 1E7 gives non-trivial integer values +--luaU.TEST_NUMBER = 3.14159265358979323846E7 + +--[[-------------------------------------------------------------------- +-- Additional functions to handle chunk writing +-- * to use make_setS and make_setF, see test_ldump.lua elsewhere +----------------------------------------------------------------------]] + +------------------------------------------------------------------------ +-- works like the lobject.h version except that TObject used in these +-- scripts only has a 'value' field, no 'tt' field (native types used) +------------------------------------------------------------------------ +function luaU:ttype(o) + local tt = type(o.value) + if tt == "number" then return self.LUA_TNUMBER + elseif tt == "string" then return self.LUA_TSTRING + elseif tt == "nil" then return self.LUA_TNIL + elseif tt == "boolean" then return self.LUA_TBOOLEAN + else + return self.LUA_TNONE -- the rest should not appear + end +end + +------------------------------------------------------------------------ +-- create a chunk writer that writes to a string +-- * returns the writer function and a table containing the string +-- * to get the final result, look in buff.data +------------------------------------------------------------------------ +function luaU:make_setS() + local buff = {} + buff.data = "" + local writer = + function(s, buff) -- chunk writer + if not s then return end + buff.data = buff.data..s + end + return writer, buff +end + +------------------------------------------------------------------------ +-- create a chunk writer that writes to a file +-- * returns the writer function and a table containing the file handle +-- * if a nil is passed, then writer should close the open file +------------------------------------------------------------------------ +function luaU:make_setF(filename) + local buff = {} + buff.h = io.open(filename, "wb") + if not buff.h then return nil end + local writer = + function(s, buff) -- chunk writer + if not buff.h then return end + if not s then buff.h:close(); return end + buff.h:write(s) + end + return writer, buff +end + +----------------------------------------------------------------------- +-- converts a IEEE754 double number to an 8-byte little-endian string +-- * luaU:from_double() and luaU:from_int() are from ChunkBake project +-- * supports +/- Infinity, but not denormals or NaNs +----------------------------------------------------------------------- +function luaU:from_double(x) + local function grab_byte(v) + return math.floor(v / 256), + string.char(math.mod(math.floor(v), 256)) + end + local sign = 0 + if x < 0 then sign = 1; x = -x end + local mantissa, exponent = math.frexp(x) + if x == 0 then -- zero + mantissa, exponent = 0, 0 + elseif x == 1/0 then + mantissa, exponent = 0, 2047 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) + exponent = exponent + 1022 + end + local v, byte = "" -- convert to bytes + x = mantissa + for i = 1,6 do + x, byte = grab_byte(x); v = v..byte -- 47:0 + end + x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48 + x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56 + return v +end + +----------------------------------------------------------------------- +-- converts a number to a little-endian 32-bit integer string +-- * input value assumed to not overflow, can be signed/unsigned +----------------------------------------------------------------------- +function luaU:from_int(x, size) + local v = "" + x = math.floor(x) + if x >= 0 then + for i = 1, size do + v = v..string.char(math.mod(x, 256)); x = math.floor(x / 256) + end + else -- x < 0 + x = -x + local carry = 1 + for i = 1, size do + local c = 255 - math.mod(x, 256) + carry + if c == 256 then c = 0; carry = 1 else carry = 0 end + v = v..string.char(c); x = math.floor(x / 256) + end + end + return v +end + +--[[-------------------------------------------------------------------- +-- Functions to make a binary chunk +-- * many functions have the size parameter removed, since output is +-- in the form of a string and some sizes are implicit or hard-coded +-- * luaU:DumpVector has been deleted (used in DumpCode & DumpLines) +----------------------------------------------------------------------]] + +------------------------------------------------------------------------ +-- dump a block of literal bytes +------------------------------------------------------------------------ +function luaU:DumpLiteral(s, D) self:DumpBlock(s, D) end + +--[[-------------------------------------------------------------------- +-- struct DumpState: +-- L -- lua_State (not used in this script) +-- write -- lua_Chunkwriter (chunk writer function) +-- data -- void* (chunk writer context or data already written) +----------------------------------------------------------------------]] + +------------------------------------------------------------------------ +-- dumps a block of bytes +-- * lua_unlock(D.L), lua_lock(D.L) deleted +------------------------------------------------------------------------ +function luaU:DumpBlock(b, D) D.write(b, D.data) end + +------------------------------------------------------------------------ +-- dumps a single byte +------------------------------------------------------------------------ +function luaU:DumpByte(y, D) + self:DumpBlock(string.char(y), D) +end + +------------------------------------------------------------------------ +-- dumps a signed integer of size `format.int_size` (for int) +------------------------------------------------------------------------ +function luaU:DumpInt(x, D) + self:DumpBlock(self:from_int(x, format.int_size), D) +end + +------------------------------------------------------------------------ +-- dumps an unsigned integer of size `format.size_t_size` (for size_t) +------------------------------------------------------------------------ +function luaU:DumpSize(x, D) + self:DumpBlock(self:from_int(x, format.size_t_size), D) +end + +------------------------------------------------------------------------ +-- dumps a LUA_NUMBER; can be an int or double depending on the VM. +------------------------------------------------------------------------ +function luaU:DumpNumber(x, D) + if format.integral then + self:DumpBlock(self:from_int(x, format.number_size), D) + else + self:DumpBlock(self:from_double(x), D) + end +end + +------------------------------------------------------------------------ +-- dumps a Lua string +------------------------------------------------------------------------ +function luaU:DumpString(s, D) + if s == nil then + self:DumpSize(0, D) + else + s = s.."\0" -- include trailing '\0' + self:DumpSize(string.len(s), D) + self:DumpBlock(s, D) + end +end + +------------------------------------------------------------------------ +-- dumps instruction block from function prototype +------------------------------------------------------------------------ +function luaU:DumpCode(f, D) + local n = f.sizecode + self:DumpInt(n, D) + --was DumpVector + for i = 0, n - 1 do + self:DumpBlock(luaP:Instruction(f.code[i]), D) + end +end + +------------------------------------------------------------------------ +-- dumps local variable names from function prototype +------------------------------------------------------------------------ +function luaU:DumpLocals(f, D) + local n = f.sizelocvars + self:DumpInt(n, D) + for i = 0, n - 1 do + -- Dirty temporary fix: + -- `Stat{ } keeps properly count of the number of local vars, + -- but fails to keep score of their debug info (names). + -- It therefore might happen that #f.localvars < f.sizelocvars, or + -- that a variable's startpc and endpc fields are left unset. + -- FIXME: This might not be needed anymore, check the bug report + -- by J. Belmonte. + local var = f.locvars[i] + if not var then break end + -- printf("[DUMPLOCALS] dumping local var #%i = %s", i, table.tostring(var)) + self:DumpString(var.varname, D) + self:DumpInt(var.startpc or 0, D) + self:DumpInt(var.endpc or 0, D) + end +end + +------------------------------------------------------------------------ +-- dumps line information from function prototype +------------------------------------------------------------------------ +function luaU:DumpLines(f, D) + local n = f.sizelineinfo + self:DumpInt(n, D) + --was DumpVector + for i = 0, n - 1 do + self:DumpInt(f.lineinfo[i], D) -- was DumpBlock + --print(i, f.lineinfo[i]) + end +end + +------------------------------------------------------------------------ +-- dump upvalue names from function prototype +------------------------------------------------------------------------ +function luaU:DumpUpvalues(f, D) + local n = f.sizeupvalues + self:DumpInt(n, D) + for i = 0, n - 1 do + self:DumpString(f.upvalues[i], D) + end +end + +------------------------------------------------------------------------ +-- dump constant pool from function prototype +-- * nvalue(o) and tsvalue(o) macros removed +------------------------------------------------------------------------ +function luaU:DumpConstants(f, D) + local n = f.sizek + self:DumpInt(n, D) + for i = 0, n - 1 do + local o = f.k[i] -- TObject + local tt = self:ttype(o) + assert (tt >= 0) + self:DumpByte(tt, D) + if tt == self.LUA_TNUMBER then + self:DumpNumber(o.value, D) + elseif tt == self.LUA_TSTRING then + self:DumpString(o.value, D) + elseif tt == self.LUA_TBOOLEAN then + self:DumpByte (o.value and 1 or 0, D) + elseif tt == self.LUA_TNIL then + else + assert(false) -- cannot happen + end + end +end + + +function luaU:DumpProtos (f, D) + local n = f.sizep + assert (n) + self:DumpInt(n, D) + for i = 0, n - 1 do + self:DumpFunction(f.p[i], f.source, D) + end +end + +function luaU:DumpDebug(f, D) + self:DumpLines(f, D) + self:DumpLocals(f, D) + self:DumpUpvalues(f, D) +end + + +------------------------------------------------------------------------ +-- dump child function prototypes from function prototype +--FF completely reworked for 5.1 format +------------------------------------------------------------------------ +function luaU:DumpFunction(f, p, D) + -- print "Dumping function:" + -- table.print(f, 60) + + local source = f.source + if source == p then source = nil end + self:DumpString(source, D) + self:DumpInt(f.lineDefined, D) + self:DumpInt(f.lastLineDefined or 42, D) + self:DumpByte(f.nups, D) + self:DumpByte(f.numparams, D) + self:DumpByte(f.is_vararg, D) + self:DumpByte(f.maxstacksize, D) + self:DumpCode(f, D) + self:DumpConstants(f, D) + self:DumpProtos( f, D) + self:DumpDebug(f, D) +end + +------------------------------------------------------------------------ +-- dump Lua header section (some sizes hard-coded) +--FF: updated for version 5.1 +------------------------------------------------------------------------ +function luaU:DumpHeader(D) + self:DumpLiteral(format.header, D) +end + +------------------------------------------------------------------------ +-- dump function as precompiled chunk +-- * w, data are created from make_setS, make_setF +--FF: suppressed extraneous [L] param +------------------------------------------------------------------------ +function luaU:dump (Main, w, data) + local D = {} -- DumpState + D.write = w + D.data = data + self:DumpHeader(D) + self:DumpFunction(Main, nil, D) + -- added: for a chunk writer writing to a file, this final call with + -- nil data is to indicate to the writer to close the file + D.write(nil, D.data) +end + +------------------------------------------------------------------------ +-- find byte order (from lundump.c) +-- * hard-coded to little-endian +------------------------------------------------------------------------ +function luaU:endianness() + return 1 +end + +-- FIXME: ugly concat-base generation in [make_setS], bufferize properly! +function M.dump_string (proto) + local writer, buff = luaU:make_setS() + luaU:dump (proto, writer, buff) + return buff.data +end + +-- FIXME: [make_setS] sucks, perform synchronous file writing +-- Now unused +function M.dump_file (proto, filename) + local writer, buff = luaU:make_setS() + luaU:dump (proto, writer, buff) + local file = io.open (filename, "wb") + file:write (buff.data) + io.close(file) + --if UNIX_SHARPBANG then os.execute ("chmod a+x "..filename) end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lopcodes.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lopcodes.lua new file mode 100644 index 000000000..e49285e6f --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lopcodes.lua @@ -0,0 +1,442 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2005-2013 Kein-Hong Man, Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang +-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua +-- +------------------------------------------------------------------------------- + +--[[-------------------------------------------------------------------- + + $Id$ + + lopcodes.lua + Lua 5 virtual machine opcodes in Lua + This file is part of Yueliang. + + Copyright (c) 2005 Kein-Hong Man + The COPYRIGHT file describes the conditions + under which this software may be distributed. + + See the ChangeLog for more information. + +------------------------------------------------------------------------ + + [FF] Slightly modified, mainly to produce Lua 5.1 bytecode. + +----------------------------------------------------------------------]] + +--[[-------------------------------------------------------------------- +-- Notes: +-- * an Instruction is a table with OP, A, B, C, Bx elements; this +-- should allow instruction handling to work with doubles and ints +-- * Added: +-- luaP:Instruction(i): convert field elements to a 4-char string +-- luaP:DecodeInst(x): convert 4-char string into field elements +-- * WARNING luaP:Instruction outputs instructions encoded in little- +-- endian form and field size and positions are hard-coded +----------------------------------------------------------------------]] + +local function debugf() end + +local luaP = { } + +--[[ +=========================================================================== + We assume that instructions are unsigned numbers. + All instructions have an opcode in the first 6 bits. + Instructions can have the following fields: + 'A' : 8 bits + 'B' : 9 bits + 'C' : 9 bits + 'Bx' : 18 bits ('B' and 'C' together) + 'sBx' : signed Bx + + A signed argument is represented in excess K; that is, the number + value is the unsigned value minus K. K is exactly the maximum value + for that argument (so that -max is represented by 0, and +max is + represented by 2*max), which is half the maximum for the corresponding + unsigned argument. +=========================================================================== +--]] + +luaP.OpMode = {"iABC", "iABx", "iAsBx"} -- basic instruction format + +------------------------------------------------------------------------ +-- size and position of opcode arguments. +-- * WARNING size and position is hard-coded elsewhere in this script +------------------------------------------------------------------------ +luaP.SIZE_C = 9 +luaP.SIZE_B = 9 +luaP.SIZE_Bx = luaP.SIZE_C + luaP.SIZE_B +luaP.SIZE_A = 8 + +luaP.SIZE_OP = 6 + +luaP.POS_C = luaP.SIZE_OP +luaP.POS_B = luaP.POS_C + luaP.SIZE_C +luaP.POS_Bx = luaP.POS_C +luaP.POS_A = luaP.POS_B + luaP.SIZE_B + +--FF from 5.1 +luaP.BITRK = 2^(luaP.SIZE_B - 1) +function luaP:ISK(x) return x >= self.BITRK end +luaP.MAXINDEXRK = luaP.BITRK - 1 +function luaP:RKASK(x) + if x < self.BITRK then return x+self.BITRK else return x end +end + + + +------------------------------------------------------------------------ +-- limits for opcode arguments. +-- we use (signed) int to manipulate most arguments, +-- so they must fit in BITS_INT-1 bits (-1 for sign) +------------------------------------------------------------------------ +-- removed "#if SIZE_Bx < BITS_INT-1" test, assume this script is +-- running on a Lua VM with double or int as LUA_NUMBER + +luaP.MAXARG_Bx = math.ldexp(1, luaP.SIZE_Bx) - 1 +luaP.MAXARG_sBx = math.floor(luaP.MAXARG_Bx / 2) -- 'sBx' is signed + +luaP.MAXARG_A = math.ldexp(1, luaP.SIZE_A) - 1 +luaP.MAXARG_B = math.ldexp(1, luaP.SIZE_B) - 1 +luaP.MAXARG_C = math.ldexp(1, luaP.SIZE_C) - 1 + +-- creates a mask with 'n' 1 bits at position 'p' +-- MASK1(n,p) deleted +-- creates a mask with 'n' 0 bits at position 'p' +-- MASK0(n,p) deleted + +--[[-------------------------------------------------------------------- + Visual representation for reference: + + 31 | | | 0 bit position + +-----+-----+-----+----------+ + | B | C | A | Opcode | iABC format + +-----+-----+-----+----------+ + - 9 - 9 - 8 - 6 - field sizes + +-----+-----+-----+----------+ + | [s]Bx | A | Opcode | iABx | iAsBx format + +-----+-----+-----+----------+ +----------------------------------------------------------------------]] + +------------------------------------------------------------------------ +-- the following macros help to manipulate instructions +-- * changed to a table object representation, very clean compared to +-- the [nightmare] alternatives of using a number or a string +------------------------------------------------------------------------ + +-- these accept or return opcodes in the form of string names +function luaP:GET_OPCODE(i) return self.ROpCode[i.OP] end +function luaP:SET_OPCODE(i, o) i.OP = self.OpCode[o] end + +function luaP:GETARG_A(i) return i.A end +function luaP:SETARG_A(i, u) i.A = u end + +function luaP:GETARG_B(i) return i.B end +function luaP:SETARG_B(i, b) i.B = b end + +function luaP:GETARG_C(i) return i.C end +function luaP:SETARG_C(i, b) i.C = b end + +function luaP:GETARG_Bx(i) return i.Bx end +function luaP:SETARG_Bx(i, b) i.Bx = b end + +function luaP:GETARG_sBx(i) return i.Bx - self.MAXARG_sBx end +function luaP:SETARG_sBx(i, b) i.Bx = b + self.MAXARG_sBx end + +function luaP:CREATE_ABC(o,a,b,c) + return {OP = self.OpCode[o], A = a, B = b, C = c} +end + +function luaP:CREATE_ABx(o,a,bc) + return {OP = self.OpCode[o], A = a, Bx = bc} +end + +------------------------------------------------------------------------ +-- Bit shuffling stuffs +------------------------------------------------------------------------ + +if false and pcall (require, 'bit') then + ------------------------------------------------------------------------ + -- Return a 4-char string little-endian encoded form of an instruction + ------------------------------------------------------------------------ + function luaP:Instruction(i) + --FIXME + end +else + ------------------------------------------------------------------------ + -- Version without bit manipulation library. + ------------------------------------------------------------------------ + local p2 = {1,2,4,8,16,32,64,128,256, 512, 1024, 2048, 4096} + -- keeps [n] bits from [x] + local function keep (x, n) return x % p2[n+1] end + -- shifts bits of [x] [n] places to the right + local function srb (x,n) return math.floor (x / p2[n+1]) end + -- shifts bits of [x] [n] places to the left + local function slb (x,n) return x * p2[n+1] end + + ------------------------------------------------------------------------ + -- Return a 4-char string little-endian encoded form of an instruction + ------------------------------------------------------------------------ + function luaP:Instruction(i) + -- printf("Instr->string: %s %s", self.opnames[i.OP], table.tostring(i)) + local c0, c1, c2, c3 + -- change to OP/A/B/C format if needed + if i.Bx then i.C = keep (i.Bx, 9); i.B = srb (i.Bx, 9) end + -- c0 = 6B from opcode + 2LSB from A (flushed to MSB) + c0 = i.OP + slb (keep (i.A, 2), 6) + -- c1 = 6MSB from A + 2LSB from C (flushed to MSB) + c1 = srb (i.A, 2) + slb (keep (i.C, 2), 6) + -- c2 = 7MSB from C + 1LSB from B (flushed to MSB) + c2 = srb (i.C, 2) + slb (keep (i.B, 1), 7) + -- c3 = 8MSB from B + c3 = srb (i.B, 1) + --printf ("Instruction: %s %s", self.opnames[i.OP], tostringv (i)) + --printf ("Bin encoding: %x %x %x %x", c0, c1, c2, c3) + return string.char(c0, c1, c2, c3) + end +end +------------------------------------------------------------------------ +-- decodes a 4-char little-endian string into an instruction struct +------------------------------------------------------------------------ +function luaP:DecodeInst(x) + error "Not implemented" +end + +------------------------------------------------------------------------ +-- invalid register that fits in 8 bits +------------------------------------------------------------------------ +luaP.NO_REG = luaP.MAXARG_A + +------------------------------------------------------------------------ +-- R(x) - register +-- Kst(x) - constant (in constant table) +-- RK(x) == if x < MAXSTACK then R(x) else Kst(x-MAXSTACK) +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +-- grep "ORDER OP" if you change these enums +------------------------------------------------------------------------ + +--[[-------------------------------------------------------------------- +Lua virtual machine opcodes (enum OpCode): +------------------------------------------------------------------------ +name args description +------------------------------------------------------------------------ +OP_MOVE A B R(A) := R(B) +OP_LOADK A Bx R(A) := Kst(Bx) +OP_LOADBOOL A B C R(A) := (Bool)B; if (C) PC++ +OP_LOADNIL A B R(A) := ... := R(B) := nil +OP_GETUPVAL A B R(A) := UpValue[B] +OP_GETGLOBAL A Bx R(A) := Gbl[Kst(Bx)] +OP_GETTABLE A B C R(A) := R(B)[RK(C)] +OP_SETGLOBAL A Bx Gbl[Kst(Bx)] := R(A) +OP_SETUPVAL A B UpValue[B] := R(A) +OP_SETTABLE A B C R(A)[RK(B)] := RK(C) +OP_NEWTABLE A B C R(A) := {} (size = B,C) +OP_SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] +OP_ADD A B C R(A) := RK(B) + RK(C) +OP_SUB A B C R(A) := RK(B) - RK(C) +OP_MUL A B C R(A) := RK(B) * RK(C) +OP_DIV A B C R(A) := RK(B) / RK(C) +OP_POW A B C R(A) := RK(B) ^ RK(C) +OP_UNM A B R(A) := -R(B) +OP_NOT A B R(A) := not R(B) +OP_CONCAT A B C R(A) := R(B).. ... ..R(C) +OP_JMP sBx PC += sBx +OP_EQ A B C if ((RK(B) == RK(C)) ~= A) then pc++ +OP_LT A B C if ((RK(B) < RK(C)) ~= A) then pc++ +OP_LE A B C if ((RK(B) <= RK(C)) ~= A) then pc++ +OP_TEST A B C if (R(B) <=> C) then R(A) := R(B) else pc++ +OP_CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) +OP_TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1)) +OP_RETURN A B return R(A), ... ,R(A+B-2) (see note) +OP_FORLOOP A sBx R(A)+=R(A+2); if R(A) =) R(A) +OP_CLOSURE A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n)) +----------------------------------------------------------------------]] + +luaP.opnames = {} -- opcode names +luaP.OpCode = {} -- lookup name -> number +luaP.ROpCode = {} -- lookup number -> name + +local i = 0 +for v in string.gfind([[ +MOVE -- 0 +LOADK +LOADBOOL +LOADNIL +GETUPVAL +GETGLOBAL -- 5 +GETTABLE +SETGLOBAL +SETUPVAL +SETTABLE +NEWTABLE -- 10 +SELF +ADD +SUB +MUL +DIV -- 15 +MOD +POW +UNM +NOT +LEN -- 20 +CONCAT +JMP +EQ +LT +LE -- 25 +TEST +TESTSET +CALL +TAILCALL +RETURN -- 30 +FORLOOP +FORPREP +TFORLOOP +SETLIST +CLOSE -- 35 +CLOSURE +VARARG +]], "[%a]+") do + local n = "OP_"..v + luaP.opnames[i] = v + luaP.OpCode[n] = i + luaP.ROpCode[i] = n + i = i + 1 +end +luaP.NUM_OPCODES = i + +--[[ +=========================================================================== + Notes: + (1) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1, + and can be 0: OP_CALL then sets 'top' to last_result+1, so + next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use 'top'. + + (2) In OP_RETURN, if (B == 0) then return up to 'top' + + (3) For comparisons, B specifies what conditions the test should accept. + + (4) All 'skips' (pc++) assume that next instruction is a jump + + (5) OP_SETLISTO is used when the last item in a table constructor is a + function, so the number of elements set is up to top of stack +=========================================================================== +--]] + +------------------------------------------------------------------------ +-- masks for instruction properties +------------------------------------------------------------------------ +-- was enum OpModeMask: +luaP.OpModeBreg = 2 -- B is a register +luaP.OpModeBrk = 3 -- B is a register/constant +luaP.OpModeCrk = 4 -- C is a register/constant +luaP.OpModesetA = 5 -- instruction set register A +luaP.OpModeK = 6 -- Bx is a constant +luaP.OpModeT = 1 -- operator is a test + +------------------------------------------------------------------------ +-- get opcode mode, e.g. "iABC" +------------------------------------------------------------------------ +function luaP:getOpMode(m) + --printv(m) + --printv(self.OpCode[m]) + --printv(self.opmodes [self.OpCode[m]+1]) + return self.OpMode[tonumber(string.sub(self.opmodes[self.OpCode[m] + 1], 7, 7))] +end + +------------------------------------------------------------------------ +-- test an instruction property flag +-- * b is a string, e.g. "OpModeBreg" +------------------------------------------------------------------------ +function luaP:testOpMode(m, b) + return (string.sub(self.opmodes[self.OpCode[m] + 1], self[b], self[b]) == "1") +end + +-- number of list items to accumulate before a SETLIST instruction +-- (must be a power of 2) +-- * used in lparser, lvm, ldebug, ltests +luaP.LFIELDS_PER_FLUSH = 50 --FF updated to match 5.1 + +-- luaP_opnames[] is set above, as the luaP.opnames table +-- opmode(t,b,bk,ck,sa,k,m) deleted + +--[[-------------------------------------------------------------------- + Legend for luaP:opmodes: + 1 T -> T (is a test?) + 2 B -> B is a register + 3 b -> B is an RK register/constant combination + 4 C -> C is an RK register/constant combination + 5 A -> register A is set by the opcode + 6 K -> Bx is a constant + 7 m -> 1 if iABC layout, + 2 if iABx layout, + 3 if iAsBx layout +----------------------------------------------------------------------]] + +luaP.opmodes = { +-- TBbCAKm opcode + "0100101", -- OP_MOVE 0 + "0000112", -- OP_LOADK + "0000101", -- OP_LOADBOOL + "0100101", -- OP_LOADNIL + "0000101", -- OP_GETUPVAL + "0000112", -- OP_GETGLOBAL 5 + "0101101", -- OP_GETTABLE + "0000012", -- OP_SETGLOBAL + "0000001", -- OP_SETUPVAL + "0011001", -- OP_SETTABLE + "0000101", -- OP_NEWTABLE 10 + "0101101", -- OP_SELF + "0011101", -- OP_ADD + "0011101", -- OP_SUB + "0011101", -- OP_MUL + "0011101", -- OP_DIV 15 + "0011101", -- OP_MOD + "0011101", -- OP_POW + "0100101", -- OP_UNM + "0100101", -- OP_NOT + "0100101", -- OP_LEN 20 + "0101101", -- OP_CONCAT + "0000003", -- OP_JMP + "1011001", -- OP_EQ + "1011001", -- OP_LT + "1011001", -- OP_LE 25 + "1000101", -- OP_TEST + "1100101", -- OP_TESTSET + "0000001", -- OP_CALL + "0000001", -- OP_TAILCALL + "0000001", -- OP_RETURN 30 + "0000003", -- OP_FORLOOP + "0000103", -- OP_FORPREP + "1000101", -- OP_TFORLOOP + "0000001", -- OP_SETLIST + "0000001", -- OP_CLOSE 35 + "0000102", -- OP_CLOSURE + "0000101" -- OP_VARARG +} + +return luaP \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/globals.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/globals.lua new file mode 100644 index 000000000..d5f7459e9 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/globals.lua @@ -0,0 +1,86 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +--*-lua-*----------------------------------------------------------------------- +-- Override Lua's default compilation functions, so that they support Metalua +-- rather than only plain Lua +-------------------------------------------------------------------------------- + +local mlc = require 'metalua.compiler' + +local M = { } + +-- Original versions +local original_lua_versions = { + load = load, + loadfile = loadfile, + loadstring = loadstring, + dofile = dofile } + +local lua_loadstring = loadstring +local lua_loadfile = loadfile + +function M.loadstring(str, name) + if type(str) ~= 'string' then error 'string expected' end + if str:match '^\027LuaQ' then return lua_loadstring(str) end + local n = str:match '^#![^\n]*\n()' + if n then str=str:sub(n, -1) end + -- FIXME: handle erroneous returns (return nil + error msg) + return mlc.new():src_to_function(str, name) +end + +function M.loadfile(filename) + local f, err_msg = io.open(filename, 'rb') + if not f then return nil, err_msg end + local success, src = pcall( f.read, f, '*a') + pcall(f.close, f) + if success then return M.loadstring (src, '@'..filename) + else return nil, src end +end + +function M.load(f, name) + local acc = { } + while true do + local x = f() + if not x then break end + assert(type(x)=='string', "function passed to load() must return strings") + table.insert(acc, x) + end + return M.loadstring(table.concat(acc)) +end + +function M.dostring(src) + local f, msg = M.loadstring(src) + if not f then error(msg) end + return f() +end + +function M.dofile(name) + local f, msg = M.loadfile(name) + if not f then error(msg) end + return f() +end + +-- Export replacement functions as globals +for name, f in pairs(M) do _G[name] = f end + +-- To be done *after* exportation +M.lua = original_lua_versions + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser.lua new file mode 100644 index 000000000..74997aef2 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser.lua @@ -0,0 +1,42 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-- Export all public APIs from sub-modules, squashed into a flat spacename + +local MT = { __type='metalua.compiler.parser' } + +local MODULE_REL_NAMES = { "annot.grammar", "expr", "meta", "misc", + "stat", "table", "ext" } + +local function new() + local M = { + lexer = require "metalua.compiler.parser.lexer" (); + extensions = { } } + for _, rel_name in ipairs(MODULE_REL_NAMES) do + local abs_name = "metalua.compiler.parser."..rel_name + local extender = require (abs_name) + if not M.extensions[abs_name] then + if type (extender) == 'function' then extender(M) end + M.extensions[abs_name] = extender + end + end + return setmetatable(M, MT) +end + +return { new = new } diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/generator.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/generator.lua new file mode 100644 index 000000000..3a805ad42 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/generator.lua @@ -0,0 +1,48 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local checks = require 'checks' +local gg = require 'metalua.grammar.generator' +local M = { } + +function M.opt(mlc, primary, a_type) + checks('table', 'table|function', 'string') + return gg.sequence{ + primary, + gg.onkeyword{ "#", function() return assert(mlc.annot[a_type]) end }, + builder = function(x) + local t, annot = unpack(x) + return annot and { tag='Annot', t, annot } or t + end } +end + +-- split a list of "foo" and "`Annot{foo, annot}" into a list of "foo" +-- and a list of "annot". +-- No annot list is returned if none of the elements were annotated. +function M.split(lst) + local x, a, some = { }, { }, false + for i, p in ipairs(lst) do + if p.tag=='Annot' then + some, x[i], a[i] = true, unpack(p) + else x[i] = p end + end + if some then return x, a else return lst end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/grammar.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/grammar.lua new file mode 100644 index 000000000..7ce3ec41b --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/annot/grammar.lua @@ -0,0 +1,112 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local gg = require 'metalua.grammar.generator' + +return function(M) + local _M = gg.future(M) + M.lexer :add '->' + local A = { } + local _A = gg.future(A) + M.annot = A + + -- Type identifier: Lua keywords such as `"nil"` allowed. + function M.annot.tid(lx) + local w = lx :next() + local t = w.tag + if t=='Keyword' and w[1] :match '^[%a_][%w_]*$' or w.tag=='Id' + then return {tag='TId'; lineinfo=w.lineinfo; w[1]} + else return gg.parse_error (lx, 'tid expected') end + end + + local field_types = { var='TVar'; const='TConst'; + currently='TCurrently'; field='TField' } + + -- TODO check lineinfo + function M.annot.tf(lx) + local tk = lx:next() + local w = tk[1] + local tag = field_types[w] + if not tag then error ('Invalid field type '..w) + elseif tag=='TField' then return {tag='TField'} else + local te = M.te(lx) + return {tag=tag; te} + end + end + + M.annot.tebar_content = gg.list{ + name = 'tebar content', + primary = _A.te, + separators = { ",", ";" }, + terminators = ")" } + + M.annot.tebar = gg.multisequence{ + name = 'annot.tebar', + --{ '*', builder = 'TDynbar' }, -- maybe not user-available + { '(', _A.tebar_content, ')', + builder = function(x) return x[1] end }, + { _A.te } + } + + M.annot.te = gg.multisequence{ + name = 'annot.te', + { _A.tid, builder=function(x) return x[1] end }, + { '*', builder = 'TDyn' }, + { "[", + gg.list{ + primary = gg.sequence{ + _M.expr, "=", _A.tf, + builder = 'TPair' + }, + separators = { ",", ";" }, + terminators = { "]", "|" } }, + gg.onkeyword{ "|", _A.tf }, + "]", + builder = function(x) + local fields, other = unpack(x) + return { tag='TTable', other or {tag='TField'}, fields } + end }, -- "[ ... ]" + { '(', _A.tebar_content, ')', '->', '(', _A.tebar_content, ')', + builder = function(x) + local p, r = unpack(x) + return {tag='TFunction', p, r } + end } } + + M.annot.ts = gg.multisequence{ + name = 'annot.ts', + { 'return', _A.tebar_content, builder='TReturn' }, + { _A.tid, builder = function(x) + if x[1][1]=='pass' then return {tag='TPass'} + else error "Bad statement type" end + end } } + +-- TODO: add parsers for statements: +-- #return tebar +-- #alias = te +-- #ell = tf +--[[ + M.annot.stat_annot = gg.sequence{ + gg.list{ primary=_A.tid, separators='.' }, + '=', + XXX??, + builder = 'Annot' } +--]] + + return M.annot +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/expr.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/expr.lua new file mode 100644 index 000000000..8ce4677a5 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/expr.lua @@ -0,0 +1,206 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Exported API: +-- * [mlp.expr()] +-- * [mlp.expr_list()] +-- * [mlp.func_val()] +-- +------------------------------------------------------------------------------- + +local pp = require 'metalua.pprint' +local gg = require 'metalua.grammar.generator' +local annot = require 'metalua.compiler.parser.annot.generator' + +return function(M) + local _M = gg.future(M) + local _table = gg.future(M, 'table') + local _meta = gg.future(M, 'meta') -- TODO move to ext? + local _annot = gg.future(M, 'annot') -- TODO move to annot + + -------------------------------------------------------------------------------- + -- Non-empty expression list. Actually, this isn't used here, but that's + -- handy to give to users. + -------------------------------------------------------------------------------- + M.expr_list = gg.list{ primary=_M.expr, separators="," } + + -------------------------------------------------------------------------------- + -- Helpers for function applications / method applications + -------------------------------------------------------------------------------- + M.func_args_content = gg.list{ + name = "function arguments", + primary = _M.expr, + separators = ",", + terminators = ")" } + + -- Used to parse methods + M.method_args = gg.multisequence{ + name = "function argument(s)", + { "{", _table.content, "}" }, + { "(", _M.func_args_content, ")", builder = unpack }, + { "+{", _meta.quote_content, "}" }, + -- TODO lineinfo? + function(lx) local r = M.opt_string(lx); return r and {r} or { } end } + + -------------------------------------------------------------------------------- + -- [func_val] parses a function, from opening parameters parenthese to + -- "end" keyword included. Used for anonymous functions as well as + -- function declaration statements (both local and global). + -- + -- It's wrapped in a [_func_val] eta expansion, so that when expr + -- parser uses the latter, they will notice updates of [func_val] + -- definitions. + -------------------------------------------------------------------------------- + M.func_params_content = gg.list{ + name="function parameters", + gg.multisequence{ { "...", builder = "Dots" }, annot.opt(M, _M.id, 'te') }, + separators = ",", terminators = {")", "|"} } + + -- TODO move to annot + M.func_val = gg.sequence{ + name = "function body", + "(", _M.func_params_content, ")", _M.block, "end", + builder = function(x) + local params, body = unpack(x) + local annots, some = { }, false + for i, p in ipairs(params) do + if p.tag=='Annot' then + params[i], annots[i], some = p[1], p[2], true + else annots[i] = false end + end + if some then return { tag='Function', params, body, annots } + else return { tag='Function', params, body } end + end } + + local func_val = function(lx) return M.func_val(lx) end + + -------------------------------------------------------------------------------- + -- Default parser for primary expressions + -------------------------------------------------------------------------------- + function M.id_or_literal (lx) + local a = lx:next() + if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then + local msg + if a.tag=='Eof' then + msg = "End of file reached when an expression was expected" + elseif a.tag=='Keyword' then + msg = "An expression was expected, and `"..a[1].. + "' can't start an expression" + else + msg = "Unexpected expr token " .. pp.tostring (a) + end + gg.parse_error (lx, msg) + end + return a + end + + + -------------------------------------------------------------------------------- + -- Builder generator for operators. Wouldn't be worth it if "|x|" notation + -- were allowed, but then lua 5.1 wouldn't compile it + -------------------------------------------------------------------------------- + + -- opf1 = |op| |_,a| `Op{ op, a } + local function opf1 (op) return + function (_,a) return { tag="Op", op, a } end end + + -- opf2 = |op| |a,_,b| `Op{ op, a, b } + local function opf2 (op) return + function (a,_,b) return { tag="Op", op, a, b } end end + + -- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed) + local function opf2r (op) return + function (a,_,b) return { tag="Op", op, b, a } end end + + local function op_ne(a, _, b) + -- This version allows to remove the "ne" operator from the AST definition. + -- However, it doesn't always produce the exact same bytecode as Lua 5.1. + return { tag="Op", "not", + { tag="Op", "eq", a, b, lineinfo= { + first = a.lineinfo.first, last = b.lineinfo.last } } } + end + + + -------------------------------------------------------------------------------- + -- + -- complete expression + -- + -------------------------------------------------------------------------------- + + -- FIXME: set line number. In [expr] transformers probably + M.expr = gg.expr { + name = "expression", + primary = gg.multisequence{ + name = "expr primary", + { "(", _M.expr, ")", builder = "Paren" }, + { "function", _M.func_val, builder = unpack }, + { "-{", _meta.splice_content, "}", builder = unpack }, + { "+{", _meta.quote_content, "}", builder = unpack }, + { "nil", builder = "Nil" }, + { "true", builder = "True" }, + { "false", builder = "False" }, + { "...", builder = "Dots" }, + { "{", _table.content, "}", builder = unpack }, + _M.id_or_literal }, + + infix = { + name = "expr infix op", + { "+", prec = 60, builder = opf2 "add" }, + { "-", prec = 60, builder = opf2 "sub" }, + { "*", prec = 70, builder = opf2 "mul" }, + { "/", prec = 70, builder = opf2 "div" }, + { "%", prec = 70, builder = opf2 "mod" }, + { "^", prec = 90, builder = opf2 "pow", assoc = "right" }, + { "..", prec = 40, builder = opf2 "concat", assoc = "right" }, + { "==", prec = 30, builder = opf2 "eq" }, + { "~=", prec = 30, builder = op_ne }, + { "<", prec = 30, builder = opf2 "lt" }, + { "<=", prec = 30, builder = opf2 "le" }, + { ">", prec = 30, builder = opf2r "lt" }, + { ">=", prec = 30, builder = opf2r "le" }, + { "and",prec = 20, builder = opf2 "and" }, + { "or", prec = 10, builder = opf2 "or" } }, + + prefix = { + name = "expr prefix op", + { "not", prec = 80, builder = opf1 "not" }, + { "#", prec = 80, builder = opf1 "len" }, + { "-", prec = 80, builder = opf1 "unm" } }, + + suffix = { + name = "expr suffix op", + { "[", _M.expr, "]", builder = function (tab, idx) + return {tag="Index", tab, idx[1]} end}, + { ".", _M.id, builder = function (tab, field) + return {tag="Index", tab, _M.id2string(field[1])} end }, + { "(", _M.func_args_content, ")", builder = function(f, args) + return {tag="Call", f, unpack(args[1])} end }, + { "{", _table.content, "}", builder = function (f, arg) + return {tag="Call", f, arg[1]} end}, + { ":", _M.id, _M.method_args, builder = function (obj, post) + local m_name, args = unpack(post) + return {tag="Invoke", obj, _M.id2string(m_name), unpack(args)} end}, + { "+{", _meta.quote_content, "}", builder = function (f, arg) + return {tag="Call", f, arg[1] } end }, + default = { name="opt_string_arg", parse = _M.opt_string, builder = function(f, arg) + return {tag="Call", f, arg } end } } } + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/ext.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/ext.lua new file mode 100644 index 000000000..4e9d3950f --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/ext.lua @@ -0,0 +1,96 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Non-Lua syntax extensions +-- +-------------------------------------------------------------------------------- + +local gg = require 'metalua.grammar.generator' + +return function(M) + + local _M = gg.future(M) + + --------------------------------------------------------------------------- + -- Algebraic Datatypes + ---------------------------------------------------------------------------- + local function adt (lx) + local node = _M.id (lx) + local tagval = node[1] + -- tagkey = `Pair{ `String "key", `String{ -{tagval} } } + local tagkey = { tag="Pair", {tag="String", "tag"}, {tag="String", tagval} } + if lx:peek().tag == "String" or lx:peek().tag == "Number" then + -- TODO support boolean litterals + return { tag="Table", tagkey, lx:next() } + elseif lx:is_keyword (lx:peek(), "{") then + local x = M.table.table (lx) + table.insert (x, 1, tagkey) + return x + else return { tag="Table", tagkey } end + end + + M.adt = gg.sequence{ "`", adt, builder = unpack } + + M.expr.primary :add(M.adt) + + ---------------------------------------------------------------------------- + -- Anonymous lambda + ---------------------------------------------------------------------------- + M.lambda_expr = gg.sequence{ + "|", _M.func_params_content, "|", _M.expr, + builder = function (x) + local li = x[2].lineinfo + return { tag="Function", x[1], + { {tag="Return", x[2], lineinfo=li }, lineinfo=li } } + end } + + M.expr.primary :add (M.lambda_expr) + + -------------------------------------------------------------------------------- + -- Allows to write "a `f` b" instead of "f(a, b)". Taken from Haskell. + -------------------------------------------------------------------------------- + function M.expr_in_backquotes (lx) return M.expr(lx, 35) end -- 35=limited precedence + M.expr.infix :add{ name = "infix function", + "`", _M.expr_in_backquotes, "`", prec = 35, assoc="left", + builder = function(a, op, b) return {tag="Call", op[1], a, b} end } + + -------------------------------------------------------------------------------- + -- C-style op+assignments + -- TODO: no protection against side-effects in LHS vars. + -------------------------------------------------------------------------------- + local function op_assign(kw, op) + local function rhs(a, b) return { tag="Op", op, a, b } end + local function f(a,b) + if #a ~= #b then gg.parse_error "assymetric operator+assignment" end + local right = { } + local r = { tag="Set", a, right } + for i=1, #a do right[i] = { tag="Op", op, a[i], b[i] } end + return r + end + M.lexer :add (kw) + M.assignments[kw] = f + end + + local ops = { add='+='; sub='-='; mul='*='; div='/=' } + for ast_op_name, keyword in pairs(ops) do op_assign(keyword, ast_op_name) end + + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/lexer.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/lexer.lua new file mode 100644 index 000000000..2b5ff7e9d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/lexer.lua @@ -0,0 +1,43 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2014 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +---------------------------------------------------------------------- +-- Generate a new lua-specific lexer, derived from the generic lexer. +---------------------------------------------------------------------- + +local generic_lexer = require 'metalua.grammar.lexer' + +return function() + local lexer = generic_lexer.lexer :clone() + + local keywords = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", + "goto", -- Lua5.2 + "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "...", "..", "==", ">=", "<=", "~=", + "::", -- Lua5,2 + "+{", "-{" } -- Metalua + + for _, w in ipairs(keywords) do lexer :add (w) end + + return lexer +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/meta.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/meta.lua new file mode 100644 index 000000000..71eb3c358 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/meta.lua @@ -0,0 +1,138 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2014 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +-- Compile-time metaprogramming features: splicing ASTs generated during compilation, +-- AST quasi-quoting helpers. + +local gg = require 'metalua.grammar.generator' + +return function(M) + local _M = gg.future(M) + M.meta={ } + local _MM = gg.future(M.meta) + + -------------------------------------------------------------------------------- + -- External splicing: compile an AST into a chunk, load and evaluate + -- that chunk, and replace the chunk by its result (which must also be + -- an AST). + -------------------------------------------------------------------------------- + + -- TODO: that's not part of the parser + function M.meta.eval (ast) + -- TODO: should there be one mlc per splice, or per parser instance? + local mlc = require 'metalua.compiler'.new() + local f = mlc :ast_to_function (ast, '=splice') + local result=f(M) -- splices act on the current parser + return result + end + + ---------------------------------------------------------------------------- + -- Going from an AST to an AST representing that AST + -- the only hash-part key being lifted is `"tag"`. + -- Doesn't lift subtrees protected inside a `Splice{ ... }. + -- e.g. change `Foo{ 123 } into + -- `Table{ `Pair{ `String "tag", `String "foo" }, `Number 123 } + ---------------------------------------------------------------------------- + local function lift (t) + --print("QUOTING:", table.tostring(t, 60,'nohash')) + local cases = { } + function cases.table (t) + local mt = { tag = "Table" } + --table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } }) + if t.tag == "Splice" then + assert (#t==1, "Invalid splice") + local sp = t[1] + return sp + elseif t.tag then + table.insert (mt, { tag="Pair", lift "tag", lift(t.tag) }) + end + for _, v in ipairs (t) do + table.insert (mt, lift(v)) + end + return mt + end + function cases.number (t) return { tag = "Number", t, quote = true } end + function cases.string (t) return { tag = "String", t, quote = true } end + function cases.boolean (t) return { tag = t and "True" or "False", t, quote = true } end + local f = cases [type(t)] + if f then return f(t) else error ("Cannot quote an AST containing "..tostring(t)) end + end + M.meta.lift = lift + + -------------------------------------------------------------------------------- + -- when this variable is false, code inside [-{...}] is compiled and + -- avaluated immediately. When it's true (supposedly when we're + -- parsing data inside a quasiquote), [-{foo}] is replaced by + -- [`Splice{foo}], which will be unpacked by [quote()]. + -------------------------------------------------------------------------------- + local in_a_quote = false + + -------------------------------------------------------------------------------- + -- Parse the inside of a "-{ ... }" + -------------------------------------------------------------------------------- + function M.meta.splice_content (lx) + local parser_name = "expr" + if lx:is_keyword (lx:peek(2), ":") then + local a = lx:next() + lx:next() -- skip ":" + assert (a.tag=="Id", "Invalid splice parser name") + parser_name = a[1] + end + -- TODO FIXME running a new parser with the old lexer?! + local parser = require 'metalua.compiler.parser'.new() + local ast = parser [parser_name](lx) + if in_a_quote then -- only prevent quotation in this subtree + --printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60)) + return { tag="Splice", ast } + else -- convert in a block, eval, replace with result + if parser_name == "expr" then ast = { { tag="Return", ast } } + elseif parser_name == "stat" then ast = { ast } + elseif parser_name ~= "block" then + error ("splice content must be an expr, stat or block") end + --printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60)) + return M.meta.eval (ast) + end + end + + M.meta.splice = gg.sequence{ "-{", _MM.splice_content, "}", builder=unpack } + + -------------------------------------------------------------------------------- + -- Parse the inside of a "+{ ... }" + -------------------------------------------------------------------------------- + function M.meta.quote_content (lx) + local parser + if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content } + local parser_name = M.id(lx)[1] + parser = M[parser_name] + lx:next() -- skip ":" + else -- +{ content } + parser = M.expr + end + + local prev_iq = in_a_quote + in_a_quote = true + --print("IN_A_QUOTE") + local content = parser (lx) + local q_content = M.meta.lift (content) + in_a_quote = prev_iq + return q_content + end + + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/misc.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/misc.lua new file mode 100644 index 000000000..f7dde0923 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/misc.lua @@ -0,0 +1,176 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Summary: metalua parser, miscellaneous utility functions. +-- +------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Exported API: +-- * [mlp.fget()] +-- * [mlp.id()] +-- * [mlp.opt_id()] +-- * [mlp.id_list()] +-- * [mlp.string()] +-- * [mlp.opt_string()] +-- * [mlp.id2string()] +-- +-------------------------------------------------------------------------------- + +local pp = require 'metalua.pprint' +local gg = require 'metalua.grammar.generator' + +-- TODO: replace splice-aware versions with naive ones, move etensions in ./meta + +return function(M) + local _M = gg.future(M) + +--[[ metaprog-free versions: + function M.id(lx) + if lx:peek().tag~='Id' then gg.parse_error(lx, "Identifier expected") + else return lx:next() end + end + + function M.opt_id(lx) + if lx:peek().tag~='Id' then return lx:next() else return false end + end + + function M.string(lx) + if lx:peek().tag~='String' then gg.parse_error(lx, "String expected") + else return lx:next() end + end + + function M.opt_string(lx) + if lx:peek().tag~='String' then return lx:next() else return false end + end + + -------------------------------------------------------------------------------- + -- Converts an identifier into a string. Hopefully one day it'll handle + -- splices gracefully, but that proves quite tricky. + -------------------------------------------------------------------------------- + function M.id2string (id) + if id.tag == "Id" then id.tag = "String"; return id + else error ("Identifier expected: "..table.tostring(id, 'nohash')) end + end +--]] + + -------------------------------------------------------------------------------- + -- Try to read an identifier (possibly as a splice), or return [false] if no + -- id is found. + -------------------------------------------------------------------------------- + function M.opt_id (lx) + local a = lx:peek(); + if lx:is_keyword (a, "-{") then + local v = M.meta.splice(lx) + if v.tag ~= "Id" and v.tag ~= "Splice" then + gg.parse_error(lx, "Bad id splice") + end + return v + elseif a.tag == "Id" then return lx:next() + else return false end + end + + -------------------------------------------------------------------------------- + -- Mandatory reading of an id: causes an error if it can't read one. + -------------------------------------------------------------------------------- + function M.id (lx) + return M.opt_id (lx) or gg.parse_error(lx,"Identifier expected") + end + + -------------------------------------------------------------------------------- + -- Common helper function + -------------------------------------------------------------------------------- + M.id_list = gg.list { primary = _M.id, separators = "," } + + -------------------------------------------------------------------------------- + -- Converts an identifier into a string. Hopefully one day it'll handle + -- splices gracefully, but that proves quite tricky. + -------------------------------------------------------------------------------- + function M.id2string (id) + --print("id2string:", disp.ast(id)) + if id.tag == "Id" then id.tag = "String"; return id + elseif id.tag == "Splice" then + error ("id2string on splice not implemented") + -- Evaluating id[1] will produce `Id{ xxx }, + -- and we want it to produce `String{ xxx }. + -- The following is the plain notation of: + -- +{ `String{ `Index{ `Splice{ -{id[1]} }, `Number 1 } } } + return { tag="String", { tag="Index", { tag="Splice", id[1] }, + { tag="Number", 1 } } } + else error ("Identifier expected: "..pp.tostring (id, {metalua_tag=1, hide_hash=1})) end + end + + -------------------------------------------------------------------------------- + -- Read a string, possibly spliced, or return an error if it can't + -------------------------------------------------------------------------------- + function M.string (lx) + local a = lx:peek() + if lx:is_keyword (a, "-{") then + local v = M.meta.splice(lx) + if v.tag ~= "String" and v.tag ~= "Splice" then + gg.parse_error(lx,"Bad string splice") + end + return v + elseif a.tag == "String" then return lx:next() + else error "String expected" end + end + + -------------------------------------------------------------------------------- + -- Try to read a string, or return false if it can't. No splice allowed. + -------------------------------------------------------------------------------- + function M.opt_string (lx) + return lx:peek().tag == "String" and lx:next() + end + + -------------------------------------------------------------------------------- + -- Chunk reader: block + Eof + -------------------------------------------------------------------------------- + function M.skip_initial_sharp_comment (lx) + -- Dirty hack: I'm happily fondling lexer's private parts + -- FIXME: redundant with lexer:newstream() + lx :sync() + local i = lx.src:match ("^#.-\n()", lx.i) + if i then + lx.i = i + lx.column_offset = i + lx.line = lx.line and lx.line + 1 or 1 + end + end + + local function chunk (lx) + if lx:peek().tag == 'Eof' then + return { } -- handle empty files + else + M.skip_initial_sharp_comment (lx) + local chunk = M.block (lx) + if lx:peek().tag ~= "Eof" then + gg.parse_error(lx, "End-of-file expected") + end + return chunk + end + end + + -- chunk is wrapped in a sequence so that it has a "transformer" field. + M.chunk = gg.sequence { chunk, builder = unpack } + + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/stat.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/stat.lua new file mode 100644 index 000000000..5d5e3a91d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/stat.lua @@ -0,0 +1,279 @@ +------------------------------------------------------------------------------ +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Summary: metalua parser, statement/block parser. This is part of the +-- definition of module [mlp]. +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Exports API: +-- * [mlp.stat()] +-- * [mlp.block()] +-- * [mlp.for_header()] +-- +------------------------------------------------------------------------------- + +local lexer = require 'metalua.grammar.lexer' +local gg = require 'metalua.grammar.generator' + +local annot = require 'metalua.compiler.parser.annot.generator' + +-------------------------------------------------------------------------------- +-- List of all keywords that indicate the end of a statement block. Users are +-- likely to extend this list when designing extensions. +-------------------------------------------------------------------------------- + + +return function(M) + local _M = gg.future(M) + + M.block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" } + + -- FIXME: this must be handled from within GG!!! + -- FIXME: there's no :add method in the list anyway. Added by gg.list?! + function M.block_terminators :add(x) + if type (x) == "table" then for _, y in ipairs(x) do self :add (y) end + else table.insert (self, x) end + end + + ---------------------------------------------------------------------------- + -- list of statements, possibly followed by semicolons + ---------------------------------------------------------------------------- + M.block = gg.list { + name = "statements block", + terminators = M.block_terminators, + primary = function (lx) + -- FIXME use gg.optkeyword() + local x = M.stat (lx) + if lx:is_keyword (lx:peek(), ";") then lx:next() end + return x + end } + + ---------------------------------------------------------------------------- + -- Helper function for "return " parsing. + -- Called when parsing return statements. + -- The specific test for initial ";" is because it's not a block terminator, + -- so without it gg.list would choke on "return ;" statements. + -- We don't make a modified copy of block_terminators because this list + -- is sometimes modified at runtime, and the return parser would get out of + -- sync if it was relying on a copy. + ---------------------------------------------------------------------------- + local return_expr_list_parser = gg.multisequence{ + { ";" , builder = function() return { } end }, + default = gg.list { + _M.expr, separators = ",", terminators = M.block_terminators } } + + + local for_vars_list = gg.list{ + name = "for variables list", + primary = _M.id, + separators = ",", + terminators = "in" } + + ---------------------------------------------------------------------------- + -- for header, between [for] and [do] (exclusive). + -- Return the `Forxxx{...} AST, without the body element (the last one). + ---------------------------------------------------------------------------- + function M.for_header (lx) + local vars = M.id_list(lx) + if lx :is_keyword (lx:peek(), "=") then + if #vars ~= 1 then + gg.parse_error (lx, "numeric for only accepts one variable") + end + lx:next() -- skip "=" + local exprs = M.expr_list (lx) + if #exprs < 2 or #exprs > 3 then + gg.parse_error (lx, "numeric for requires 2 or 3 boundaries") + end + return { tag="Fornum", vars[1], unpack (exprs) } + else + if not lx :is_keyword (lx :next(), "in") then + gg.parse_error (lx, '"=" or "in" expected in for loop') + end + local exprs = M.expr_list (lx) + return { tag="Forin", vars, exprs } + end + end + + ---------------------------------------------------------------------------- + -- Function def parser helper: id ( . id ) * + ---------------------------------------------------------------------------- + local function fn_builder (list) + local acc = list[1] + local first = acc.lineinfo.first + for i = 2, #list do + local index = M.id2string(list[i]) + local li = lexer.new_lineinfo(first, index.lineinfo.last) + acc = { tag="Index", acc, index, lineinfo=li } + end + return acc + end + local func_name = gg.list{ _M.id, separators = ".", builder = fn_builder } + + ---------------------------------------------------------------------------- + -- Function def parser helper: ( : id )? + ---------------------------------------------------------------------------- + local method_name = gg.onkeyword{ name = "method invocation", ":", _M.id, + transformers = { function(x) return x and x.tag=='Id' and M.id2string(x) end } } + + ---------------------------------------------------------------------------- + -- Function def builder + ---------------------------------------------------------------------------- + local function funcdef_builder(x) + local name, method, func = unpack(x) + if method then + name = { tag="Index", name, method, + lineinfo = { + first = name.lineinfo.first, + last = method.lineinfo.last } } + table.insert (func[1], 1, {tag="Id", "self"}) + end + local r = { tag="Set", {name}, {func} } + r[1].lineinfo = name.lineinfo + r[2].lineinfo = func.lineinfo + return r + end + + + ---------------------------------------------------------------------------- + -- if statement builder + ---------------------------------------------------------------------------- + local function if_builder (x) + local cond_block_pairs, else_block, r = x[1], x[2], {tag="If"} + local n_pairs = #cond_block_pairs + for i = 1, n_pairs do + local cond, block = unpack(cond_block_pairs[i]) + r[2*i-1], r[2*i] = cond, block + end + if else_block then table.insert(r, #r+1, else_block) end + return r + end + + -------------------------------------------------------------------------------- + -- produce a list of (expr,block) pairs + -------------------------------------------------------------------------------- + local elseifs_parser = gg.list { + gg.sequence { _M.expr, "then", _M.block , name='elseif parser' }, + separators = "elseif", + terminators = { "else", "end" } + } + + local annot_expr = gg.sequence { + _M.expr, + gg.onkeyword{ "#", gg.future(M, 'annot').tf }, + builder = function(x) + local e, a = unpack(x) + if a then return { tag='Annot', e, a } + else return e end + end } + + local annot_expr_list = gg.list { + primary = annot.opt(M, _M.expr, 'tf'), separators = ',' } + + ------------------------------------------------------------------------ + -- assignments and calls: statements that don't start with a keyword + ------------------------------------------------------------------------ + local function assign_or_call_stat_parser (lx) + local e = annot_expr_list (lx) + local a = lx:is_keyword(lx:peek()) + local op = a and M.assignments[a] + -- TODO: refactor annotations + if op then + --FIXME: check that [e] is a LHS + lx :next() + local annots + e, annots = annot.split(e) + local v = M.expr_list (lx) + if type(op)=="string" then return { tag=op, e, v, annots } + else return op (e, v) end + else + assert (#e > 0) + if #e > 1 then + gg.parse_error (lx, + "comma is not a valid statement separator; statement can be ".. + "separated by semicolons, or not separated at all") + elseif e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then + local typename + if e[1].tag == 'Id' then + typename = '("'..e[1][1]..'") is an identifier' + elseif e[1].tag == 'Op' then + typename = "is an arithmetic operation" + else typename = "is of type '"..(e[1].tag or "").."'" end + gg.parse_error (lx, + "This expression %s; ".. + "a statement was expected, and only function and method call ".. + "expressions can be used as statements", typename); + end + return e[1] + end + end + + M.local_stat_parser = gg.multisequence{ + -- local function + { "function", _M.id, _M.func_val, builder = + function(x) + local vars = { x[1], lineinfo = x[1].lineinfo } + local vals = { x[2], lineinfo = x[2].lineinfo } + return { tag="Localrec", vars, vals } + end }, + -- local ( = )? + default = gg.sequence{ + gg.list{ + primary = annot.opt(M, _M.id, 'tf'), + separators = ',' }, + gg.onkeyword{ "=", _M.expr_list }, + builder = function(x) + local annotated_left, right = unpack(x) + local left, annotations = annot.split(annotated_left) + return {tag="Local", left, right or { }, annotations } + end } } + + ------------------------------------------------------------------------ + -- statement + ------------------------------------------------------------------------ + M.stat = gg.multisequence { + name = "statement", + { "do", _M.block, "end", builder = + function (x) return { tag="Do", unpack (x[1]) } end }, + { "for", _M.for_header, "do", _M.block, "end", builder = + function (x) x[1][#x[1]+1] = x[2]; return x[1] end }, + { "function", func_name, method_name, _M.func_val, builder=funcdef_builder }, + { "while", _M.expr, "do", _M.block, "end", builder = "While" }, + { "repeat", _M.block, "until", _M.expr, builder = "Repeat" }, + { "local", _M.local_stat_parser, builder = unpack }, + { "return", return_expr_list_parser, builder = + function(x) x[1].tag='Return'; return x[1] end }, + { "break", builder = function() return { tag="Break" } end }, + { "-{", gg.future(M, 'meta').splice_content, "}", builder = unpack }, + { "if", gg.nonempty(elseifs_parser), gg.onkeyword{ "else", M.block }, "end", + builder = if_builder }, + default = assign_or_call_stat_parser } + + M.assignments = { + ["="] = "Set" + } + + function M.assignments:add(k, v) self[k] = v end + + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/table.lua b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/table.lua new file mode 100644 index 000000000..11102d9ec --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/compiler/parser/table.lua @@ -0,0 +1,77 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Exported API: +-- * [M.table_bracket_field()] +-- * [M.table_field()] +-- * [M.table_content()] +-- * [M.table()] +-- +-- KNOWN BUG: doesn't handle final ";" or "," before final "}" +-- +-------------------------------------------------------------------------------- + +local gg = require 'metalua.grammar.generator' + +return function(M) + + M.table = { } + local _table = gg.future(M.table) + local _expr = gg.future(M).expr + + -------------------------------------------------------------------------------- + -- `[key] = value` table field definition + -------------------------------------------------------------------------------- + M.table.bracket_pair = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" } + + -------------------------------------------------------------------------------- + -- table element parser: list value, `id = value` pair or `[value] = value` pair. + -------------------------------------------------------------------------------- + function M.table.element (lx) + if lx :is_keyword (lx :peek(), "[") then return M.table.bracket_pair(lx) end + local e = M.expr (lx) + if not lx :is_keyword (lx :peek(), "=") then return e end + lx :next(); -- skip the "=" + local key = M.id2string(e) -- will fail on non-identifiers + local val = M.expr(lx) + local r = { tag="Pair", key, val } + r.lineinfo = { first = key.lineinfo.first, last = val.lineinfo.last } + return r + end + + ----------------------------------------------------------------------------- + -- table constructor, without enclosing braces; returns a full table object + ----------------------------------------------------------------------------- + M.table.content = gg.list { + -- eta expansion to allow patching the element definition + primary = _table.element, + separators = { ",", ";" }, + terminators = "}", + builder = "Table" } + + -------------------------------------------------------------------------------- + -- complete table constructor including [{...}] + -------------------------------------------------------------------------------- + -- TODO beware, stat and expr use only table.content, this can't be patched. + M.table.table = gg.sequence{ "{", _table.content, "}", builder = unpack } + + return M +end \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/extension/comprehension.mlua b/Utils/luarocks/share/lua/5.1/metalua/extension/comprehension.mlua new file mode 100644 index 000000000..8917b9a92 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/extension/comprehension.mlua @@ -0,0 +1,282 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- +-- +-- This extension implements list comprehensions, similar to Haskell and +-- Python syntax, to easily describe lists. +-- +-- * x[a ... b] is the list { x[a], x[a+1], ..., x[b] } +-- * { f()..., b } contains all the elements returned by f(), then b +-- (allows to expand list fields other than the last one) +-- * list comprehensions a la python, with "for" and "if" suffixes: +-- {i+10*j for i=1,3 for j=1,3 if i~=j} is { 21, 31, 12, 32, 13, 23 } +-- +------------------------------------------------------------------------------- + +-{ extension ("match", ...) } + +local SUPPORT_IMPROVED_LOOPS = true +local SUPPORT_IMPROVED_INDEXES = false -- depends on deprecated table.isub +local SUPPORT_CONTINUE = true +local SUPPORT_COMP_LISTS = true + +assert (SUPPORT_IMPROVED_LOOPS or not SUPPORT_CONTINUE, + "Can't support 'continue' without improved loop headers") + +local gg = require 'metalua.grammar.generator' +local Q = require 'metalua.treequery' + +local function dots_list_suffix_builder (x) return `DotsSuffix{ x } end + +local function for_list_suffix_builder (list_element, suffix) + local new_header = suffix[1] + match list_element with + | `Comp{ _, acc } -> table.insert (acc, new_header); return list_element + | _ -> return `Comp{ list_element, { new_header } } + end +end + +local function if_list_suffix_builder (list_element, suffix) + local new_header = `If{ suffix[1] } + match list_element with + | `Comp{ _, acc } -> table.insert (acc, new_header); return list_element + | _ -> return `Comp{ list_element, { new_header } } + end +end + +-- Builds a statement from a table element, which adds this element to +-- a table `t`, potentially thanks to an alias `tinsert` to +-- `table.insert`. +-- @param core the part around which the loops are built. +-- either `DotsSuffix{expr}, `Pair{ expr } or a plain expression +-- @param list comprehension suffixes, in the order in which they appear +-- either `Forin{ ... } or `Fornum{ ...} or `If{ ... }. In each case, +-- it misses a last child node as its body. +-- @param t a variable containing the table to fill +-- @param tinsert a variable containing `table.insert`. +-- +-- @return fill a statement which fills empty table `t` with the denoted element +local function comp_list_builder(core, list, t, tinsert) + local filler + -- 1 - Build the loop's core: if it has suffix "...", every elements of the + -- multi-return must be inserted, hence the extra [for] loop. + match core with + | `DotsSuffix{ element } -> + local x = gg.gensym() + filler = +{stat: for _, -{x} in pairs{ -{element} } do (-{tinsert})(-{t}, -{x}) end } + | `Pair{ key, value } -> + --filler = +{ -{t}[-{key}] = -{value} } + filler = `Set{ { `Index{ t, key } }, { value } } + | _ -> filler = +{ (-{tinsert})(-{t}, -{core}) } + end + + -- 2 - Stack the `if` and `for` control structures, from outside to inside. + -- This is done in a destructive way for the elements of [list]. + for i = #list, 1, -1 do + table.insert (list[i], {filler}) + filler = list[i] + end + + return filler +end + +local function table_content_builder (list) + local special = false -- Does the table need a special builder? + for _, element in ipairs(list) do + local etag = element.tag + if etag=='Comp' or etag=='DotsSuffix' then special=true; break end + end + if not special then list.tag='Table'; return list end + + local t, tinsert = gg.gensym 'table', gg.gensym 'table_insert' + local filler_block = { +{stat: local -{t}, -{tinsert} = { }, table.insert } } + for _, element in ipairs(list) do + local filler + match element with + | `Comp{ core, comp } -> filler = comp_list_builder(core, comp, t, tinsert) + | _ -> filler = comp_list_builder(element, { }, t, tinsert) + end + table.insert(filler_block, filler) + end + return `Stat{ filler_block, t } +end + + +-------------------------------------------------------------------------------- +-- Back-end for improved index operator. +local function index_builder(a, suffix) + match suffix[1] with + -- Single index, no range: keep the native semantics + | { { e, false } } -> return `Index{ a, e } + -- Either a range, or multiple indexes, or both + | ranges -> + local r = `Call{ +{table.isub}, a } + local function acc (x,y) table.insert (r,x); table.insert (r,y) end + for _, seq in ipairs (ranges) do + match seq with + | { e, false } -> acc(e,e) + | { e, f } -> acc(e,f) + end + end + return r + end +end + +------------------------------------------------------------------- +-- Find continue statements in a loop body, change them into goto +-- end-of-body. +local function transform_continue_statements(body) + local continue_statements = Q(body) + :if_unknown() -- tolerate unknown 'Continue' statements + :not_under ('Forin', 'Fornum', 'While', 'Repeat') + :filter ('Continue') + :list() + if next(continue_statements) then + local continue_label = gg.gensym 'continue' [1] + table.insert(body, `Label{ continue_label }) + for _, statement in ipairs(continue_statements) do + statement.tag = 'Goto' + statement[1] = continue_label + end + return true + else return false end +end + +------------------------------------------------------------------------------- +-- Back-end for loops with a multi-element header +local function loop_builder(x) + local first, elements, body = unpack(x) + + -- Change continue statements into gotos. + if SUPPORT_CONTINUE then transform_continue_statements(body) end + + ------------------------------------------------------------------- + -- If it's a regular loop, don't bloat the code + if not next(elements) then + table.insert(first, body) + return first + end + + ------------------------------------------------------------------- + -- There's no reason to treat the first element in a special way + table.insert(elements, 1, first) + + ------------------------------------------------------------------- + -- Change breaks into gotos that escape all loops at once. + local exit_label = nil + local function break_to_goto(break_node) + if not exit_label then exit_label = gg.gensym 'break' [1] end + break_node = break_node or { } + break_node.tag = 'Goto' + break_node[1] = exit_label + return break_node + end + Q(body) + :not_under('Function', 'Forin', 'Fornum', 'While', 'Repeat') + :filter('Break') + :foreach (break_to_goto) + + ------------------------------------------------------------------- + -- Compile all headers elements, from last to first. + -- invariant: `body` is a block (not a statement) + local result = body + for i = #elements, 1, -1 do + local e = elements[i] + match e with + | `If{ cond } -> + result = { `If{ cond, result } } + | `Until{ cond } -> + result = +{block: if -{cond} then -{break_to_goto()} else -{result} end } + | `While{ cond } -> + if i==1 then result = { `While{ cond, result } } -- top-level while + else result = +{block: if -{cond} then -{result} else -{break_to_goto()} end } end + | `Forin{ ... } | `Fornum{ ... } -> + table.insert (e, result); result={e} + | _-> require'metalua.pprint'.printf("Bad loop header element %s", e) + end + end + + + ------------------------------------------------------------------- + -- If some breaks had to be changed into gotos, insert the label + if exit_label then result = { result, `Label{ exit_label } } end + + return result +end + + +-------------------------------------------------------------------------------- +-- Improved "[...]" index operator: +-- * support for multi-indexes ("foo[bar, gnat]") +-- * support for ranges ("foo[bar ... gnat]") +-------------------------------------------------------------------------------- +local function extend(M) + + local _M = gg.future(M) + + if SUPPORT_COMP_LISTS then + -- support for "for" / "if" comprehension suffixes in literal tables + local original_table_element = M.table.element + M.table.element = gg.expr{ name="table cell", + primary = original_table_element, + suffix = { name="table cell suffix", + { "...", builder = dots_list_suffix_builder }, + { "for", _M.for_header, builder = for_list_suffix_builder }, + { "if", _M.expr, builder = if_list_suffix_builder } } } + M.table.content.builder = table_content_builder + end + + if SUPPORT_IMPROVED_INDEXES then + -- Support for ranges and multiple indices in bracket suffixes + M.expr.suffix:del '[' + M.expr.suffix:add{ name="table index/range", + "[", gg.list{ + gg.sequence { _M.expr, gg.onkeyword{ "...", _M.expr } } , + separators = { ",", ";" } }, + "]", builder = index_builder } + end + + if SUPPORT_IMPROVED_LOOPS then + local original_for_header = M.for_header + M.stat :del 'for' + M.stat :del 'while' + + M.loop_suffix = gg.multisequence{ + { 'while', _M.expr, builder = |x| `Until{ `Op{ 'not', x[1] } } }, + { 'until', _M.expr, builder = |x| `Until{ x[1] } }, + { 'if', _M.expr, builder = |x| `If{ x[1] } }, + { 'for', original_for_header, builder = |x| x[1] } } + + M.loop_suffix_list = gg.list{ _M.loop_suffix, terminators='do' } + + M.stat :add{ + 'for', original_for_header, _M.loop_suffix_list, 'do', _M.block, 'end', + builder = loop_builder } + + M.stat :add{ + 'while', _M.expr, _M.loop_suffix_list, 'do', _M.block, 'end', + builder = |x| loop_builder{ `While{x[1]}, x[2], x[3] } } + end + + if SUPPORT_CONTINUE then + M.lexer :add 'continue' + M.stat :add{ 'continue', builder='Continue' } + end +end + +return extend diff --git a/Utils/luarocks/share/lua/5.1/metalua/extension/match.mlua b/Utils/luarocks/share/lua/5.1/metalua/extension/match.mlua new file mode 100644 index 000000000..f55ff4140 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/extension/match.mlua @@ -0,0 +1,400 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Glossary: +-- +-- * term_seq: the tested stuff, a sequence of terms +-- * pattern_element: might match one term of a term seq. Represented +-- as expression ASTs. +-- * pattern_seq: might match a term_seq +-- * pattern_group: several pattern seqs, one of them might match +-- the term seq. +-- * case: pattern_group * guard option * block +-- * match_statement: tested term_seq * case list +-- +-- Hence a complete match statement is a: +-- +-- { list(expr), list{ list(list(expr)), expr or false, block } } +-- +-- Implementation hints +-- ==================== +-- +-- The implementation is made as modular as possible, so that parts +-- can be reused in other extensions. The priviledged way to share +-- contextual information across functions is through the 'cfg' table +-- argument. Its fields include: +-- +-- * code: code generated from pattern. A pattern_(element|seq|group) +-- is compiled as a sequence of instructions which will jump to +-- label [cfg.on_failure] if the tested term doesn't match. +-- +-- * on_failure: name of the label where the code will jump if the +-- pattern doesn't match +-- +-- * locals: names of local variables used by the pattern. This +-- includes bound variables, and temporary variables used to +-- destructure tables. Names are stored as keys of the table, +-- values are meaningless. +-- +-- * after_success: label where the code must jump after a pattern +-- succeeded to capture a term, and the guard suceeded if there is +-- any, and the conditional block has run. +-- +-- * ntmp: number of temporary variables used to destructurate table +-- in the current case. +-- +-- Code generation is performed by acc_xxx() functions, which accumulate +-- code in cfg.code: +-- +-- * acc_test(test, cfg) will generate a jump to cfg.on_failure +-- *when the test returns TRUE* +-- +-- * acc_stat accumulates a statement +-- +-- * acc_assign accumulate an assignment statement, and makes sure that +-- the LHS variable the registered as local in cfg.locals. +-- +------------------------------------------------------------------------------- + +-- TODO: hygiene wrt type() +-- TODO: cfg.ntmp isn't reset as often as it could. I'm not even sure +-- the corresponding locals are declared. + +local checks = require 'checks' +local gg = require 'metalua.grammar.generator' +local pp = require 'metalua.pprint' + +---------------------------------------------------------------------- +-- This would have been best done through library 'metalua.walk', +-- but walk depends on match, so we have to break the dependency. +-- It replaces all instances of `...' in `ast' with `term', unless +-- it appears in a function. +---------------------------------------------------------------------- +local function replace_dots (ast, term) + local function rec (node) + for i, child in ipairs(node) do + if type(child)~="table" then -- pass + elseif child.tag=='Dots' then + if term=='ambiguous' then + error ("You can't use `...' on the right of a match case when it appears ".. + "more than once on the left") + else node[i] = term end + elseif child.tag=='Function' then return nil + else rec(child) end + end + end + return rec(ast) +end + +local tmpvar_base = gg.gensym 'submatch.' [1] + +local function next_tmpvar(cfg) + assert (cfg.ntmp, "No cfg.ntmp imbrication level in the match compiler") + cfg.ntmp = cfg.ntmp+1 + return `Id{ tmpvar_base .. cfg.ntmp } +end + +-- Code accumulators +local acc_stat = |x,cfg| table.insert (cfg.code, x) +local acc_test = |x,cfg| acc_stat(+{stat: if -{x} then -{`Goto{cfg.on_failure}} end}, cfg) +-- lhs :: `Id{ string } +-- rhs :: expr +local function acc_assign (lhs, rhs, cfg) + assert(lhs.tag=='Id') + cfg.locals[lhs[1]] = true + acc_stat (`Set{ {lhs}, {rhs} }, cfg) +end + +local literal_tags = { String=1, Number=1, True=1, False=1, Nil=1 } + +-- pattern :: `Id{ string } +-- term :: expr +local function id_pattern_element_builder (pattern, term, cfg) + assert (pattern.tag == "Id") + if pattern[1] == "_" then + -- "_" is used as a dummy var ==> no assignment, no == checking + cfg.locals._ = true + elseif cfg.locals[pattern[1]] then + -- This var is already bound ==> test for equality + acc_test (+{ -{term} ~= -{pattern} }, cfg) + else + -- Free var ==> bind it, and remember it for latter linearity checking + acc_assign (pattern, term, cfg) + cfg.locals[pattern[1]] = true + end +end + +-- mutually recursive with table_pattern_element_builder +local pattern_element_builder + +-- pattern :: pattern and `Table{ } +-- term :: expr +local function table_pattern_element_builder (pattern, term, cfg) + local seen_dots, len = false, 0 + acc_test (+{ type( -{term} ) ~= "table" }, cfg) + for i = 1, #pattern do + local key, sub_pattern + if pattern[i].tag=="Pair" then -- Explicit key/value pair + key, sub_pattern = unpack (pattern[i]) + assert (literal_tags[key.tag], "Invalid key") + else -- Implicit key + len, key, sub_pattern = len+1, `Number{ len+1 }, pattern[i] + end + + -- '...' can only appear in final position + -- Could be fixed actually... + assert (not seen_dots, "Wrongly placed `...' ") + + if sub_pattern.tag == "Id" then + -- Optimization: save a useless [ v(n+1)=v(n).key ] + id_pattern_element_builder (sub_pattern, `Index{ term, key }, cfg) + if sub_pattern[1] ~= "_" then + acc_test (+{ -{sub_pattern} == nil }, cfg) + end + elseif sub_pattern.tag == "Dots" then + -- Remember where the capture is, and thatt arity checking shouldn't occur + seen_dots = true + else + -- Business as usual: + local v2 = next_tmpvar(cfg) + acc_assign (v2, `Index{ term, key }, cfg) + pattern_element_builder (sub_pattern, v2, cfg) + -- TODO: restore ntmp? + end + end + if seen_dots then -- remember how to retrieve `...' + -- FIXME: check, but there might be cases where the variable -{term} + -- will be overridden in contrieved tables. + -- ==> save it now, and clean the setting statement if unused + if cfg.dots_replacement then cfg.dots_replacement = 'ambiguous' + else cfg.dots_replacement = +{ select (-{`Number{len}}, unpack(-{term})) } end + else -- Check arity + acc_test (+{ #-{term} ~= -{`Number{len}} }, cfg) + end +end + +-- mutually recursive with pattern_element_builder +local eq_pattern_element_builder, regexp_pattern_element_builder + +-- Concatenate code in [cfg.code], that will jump to label +-- [cfg.on_failure] if [pattern] doesn't match [term]. [pattern] +-- should be an identifier, or at least cheap to compute and +-- side-effects free. +-- +-- pattern :: pattern_element +-- term :: expr +function pattern_element_builder (pattern, term, cfg) + if literal_tags[pattern.tag] then + acc_test (+{ -{term} ~= -{pattern} }, cfg) + elseif "Id" == pattern.tag then + id_pattern_element_builder (pattern, term, cfg) + elseif "Op" == pattern.tag and "div" == pattern[1] then + regexp_pattern_element_builder (pattern, term, cfg) + elseif "Op" == pattern.tag and "eq" == pattern[1] then + eq_pattern_element_builder (pattern, term, cfg) + elseif "Table" == pattern.tag then + table_pattern_element_builder (pattern, term, cfg) + else + error ("Invalid pattern at ".. + tostring(pattern.lineinfo).. + ": "..pp.tostring(pattern, {hide_hash=true})) + end +end + +function eq_pattern_element_builder (pattern, term, cfg) + local _, pat1, pat2 = unpack (pattern) + local ntmp_save = cfg.ntmp + pattern_element_builder (pat1, term, cfg) + cfg.ntmp = ntmp_save + pattern_element_builder (pat2, term, cfg) +end + +-- pattern :: `Op{ 'div', string, list{`Id string} or `Id{ string }} +-- term :: expr +local function regexp_pattern_element_builder (pattern, term, cfg) + local op, regexp, sub_pattern = unpack(pattern) + + -- Sanity checks -- + assert (op=='div', "Don't know what to do with that op in a pattern") + assert (regexp.tag=="String", + "Left hand side operand for '/' in a pattern must be ".. + "a literal string representing a regular expression") + if sub_pattern.tag=="Table" then + for _, x in ipairs(sub_pattern) do + assert (x.tag=="Id" or x.tag=='Dots', + "Right hand side operand for '/' in a pattern must be ".. + "a list of identifiers") + end + else + assert (sub_pattern.tag=="Id", + "Right hand side operand for '/' in a pattern must be ".. + "an identifier or a list of identifiers") + end + + -- Regexp patterns can only match strings + acc_test (+{ type(-{term}) ~= 'string' }, cfg) + -- put all captures in a list + local capt_list = +{ { string.strmatch(-{term}, -{regexp}) } } + -- save them in a var_n for recursive decomposition + local v2 = next_tmpvar(cfg) + acc_stat (+{stat: local -{v2} = -{capt_list} }, cfg) + -- was capture successful? + acc_test (+{ not next (-{v2}) }, cfg) + pattern_element_builder (sub_pattern, v2, cfg) +end + + +-- Jumps to [cfg.on_faliure] if pattern_seq doesn't match +-- term_seq. +local function pattern_seq_builder (pattern_seq, term_seq, cfg) + if #pattern_seq ~= #term_seq then error ("Bad seq arity") end + cfg.locals = { } -- reset bound variables between alternatives + for i=1, #pattern_seq do + cfg.ntmp = 1 -- reset the tmp var generator + pattern_element_builder(pattern_seq[i], term_seq[i], cfg) + end +end + +-------------------------------------------------- +-- for each case i: +-- pattern_seq_builder_i: +-- * on failure, go to on_failure_i +-- * on success, go to on_success +-- label on_success: +-- block +-- goto after_success +-- label on_failure_i +-------------------------------------------------- +local function case_builder (case, term_seq, cfg) + local patterns_group, guard, block = unpack(case) + local on_success = gg.gensym 'on_success' [1] + for i = 1, #patterns_group do + local pattern_seq = patterns_group[i] + cfg.on_failure = gg.gensym 'match_fail' [1] + cfg.dots_replacement = false + pattern_seq_builder (pattern_seq, term_seq, cfg) + if i<#patterns_group then + acc_stat (`Goto{on_success}, cfg) + acc_stat (`Label{cfg.on_failure}, cfg) + end + end + acc_stat (`Label{on_success}, cfg) + if guard then acc_test (+{not -{guard}}, cfg) end + if cfg.dots_replacement then + replace_dots (block, cfg.dots_replacement) + end + block.tag = 'Do' + acc_stat (block, cfg) + acc_stat (`Goto{cfg.after_success}, cfg) + acc_stat (`Label{cfg.on_failure}, cfg) +end + +local function match_builder (x) + local term_seq, cases = unpack(x) + local cfg = { + code = `Do{ }, + after_success = gg.gensym "_after_success" } + + + -- Some sharing issues occur when modifying term_seq, + -- so it's replaced by a copy new_term_seq. + -- TODO: clean that up, and re-suppress the useless copies + -- (cf. remarks about capture bug below). + local new_term_seq = { } + + local match_locals + + -- Make sure that all tested terms are variables or literals + for i=1, #term_seq do + local t = term_seq[i] + -- Capture problem: the following would compile wrongly: + -- `match x with x -> end' + -- Temporary workaround: suppress the condition, so that + -- all external variables are copied into unique names. + --if t.tag ~= 'Id' and not literal_tags[t.tag] then + local v = gg.gensym 'v' + if not match_locals then match_locals = `Local{ {v}, {t} } else + table.insert(match_locals[1], v) + table.insert(match_locals[2], t) + end + new_term_seq[i] = v + --end + end + term_seq = new_term_seq + + if match_locals then acc_stat(match_locals, cfg) end + + for i=1, #cases do + local case_cfg = { + after_success = cfg.after_success, + code = `Do{ } + -- locals = { } -- unnecessary, done by pattern_seq_builder + } + case_builder (cases[i], term_seq, case_cfg) + if next (case_cfg.locals) then + local case_locals = { } + table.insert (case_cfg.code, 1, `Local{ case_locals, { } }) + for v, _ in pairs (case_cfg.locals) do + table.insert (case_locals, `Id{ v }) + end + end + acc_stat(case_cfg.code, cfg) + end + local li = `String{tostring(cases.lineinfo)} + acc_stat(+{error('mismatch at '..-{li})}, cfg) + acc_stat(`Label{cfg.after_success}, cfg) + return cfg.code +end + +---------------------------------------------------------------------- +-- Syntactical front-end +---------------------------------------------------------------------- + +local function extend(M) + + local _M = gg.future(M) + + checks('metalua.compiler.parser') + M.lexer:add{ "match", "with", "->" } + M.block.terminators:add "|" + + local match_cases_list_parser = gg.list{ name = "match cases list", + gg.sequence{ name = "match case", + gg.list{ name = "match case patterns list", + primary = _M.expr_list, + separators = "|", + terminators = { "->", "if" } }, + gg.onkeyword{ "if", _M.expr, consume = true }, + "->", + _M.block }, + separators = "|", + terminators = "end" } + + M.stat:add{ name = "match statement", + "match", + _M.expr_list, + "with", gg.optkeyword "|", + match_cases_list_parser, + "end", + builder = |x| match_builder{ x[1], x[3] } } +end + +return extend \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/grammar/generator.lua b/Utils/luarocks/share/lua/5.1/metalua/grammar/generator.lua new file mode 100644 index 000000000..2680d8a53 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/grammar/generator.lua @@ -0,0 +1,834 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Summary: parser generator. Collection of higher order functors, +-- which allow to build and combine parsers. Relies on a lexer +-- that supports the same API as the one exposed in mll.lua. +-- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- +-- Exported API: +-- +-- Parser generators: +-- * [gg.sequence()] +-- * [gg.multisequence()] +-- * [gg.expr()] +-- * [gg.list()] +-- * [gg.onkeyword()] +-- * [gg.optkeyword()] +-- +-- Other functions: +-- * [gg.parse_error()] +-- * [gg.make_parser()] +-- * [gg.is_parser()] +-- +-------------------------------------------------------------------------------- + +local M = { } + +local checks = require 'checks' +local lexer = require 'metalua.grammar.lexer' +local pp = require 'metalua.pprint' + +-------------------------------------------------------------------------------- +-- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier. +-- The main purpose is to avoid variable capture in macros. +-- +-- If a string is passed as an argument, theis string will be part of the +-- id name (helpful for macro debugging) +-------------------------------------------------------------------------------- +local gensymidx = 0 + +function M.gensym (arg) + gensymidx = gensymidx + 1 + return { tag="Id", string.format(".%i.%s", gensymidx, arg or "")} +end + + +------------------------------------------------------------------------------- +-- parser metatable, which maps __call to method parse, and adds some +-- error tracing boilerplate. +------------------------------------------------------------------------------- +local parser_metatable = { } + +function parser_metatable :__call (lx, ...) + return self :parse (lx, ...) +end + +------------------------------------------------------------------------------- +-- Turn a table into a parser, mainly by setting the metatable. +------------------------------------------------------------------------------- +function M.make_parser(kind, p) + p.kind = kind + if not p.transformers then p.transformers = { } end + function p.transformers:add (x) + table.insert (self, x) + end + setmetatable (p, parser_metatable) + return p +end + +------------------------------------------------------------------------------- +-- Return true iff [x] is a parser. +-- If it's a gg-generated parser, return the name of its kind. +------------------------------------------------------------------------------- +function M.is_parser (x) + return type(x)=="function" or getmetatable(x)==parser_metatable and x.kind +end + +------------------------------------------------------------------------------- +-- Parse a sequence, without applying builder nor transformers. +------------------------------------------------------------------------------- +local function raw_parse_sequence (lx, p) + local r = { } + for i=1, #p do + local e=p[i] + if type(e) == "string" then + local kw = lx :next() + if not lx :is_keyword (kw, e) then + M.parse_error( + lx, "A keyword was expected, probably `%s'.", e) + end + elseif M.is_parser (e) then + table.insert (r, e(lx)) + else -- Invalid parser definition, this is *not* a parsing error + error(string.format( + "Sequence `%s': element #%i is neither a string nor a parser: %s", + p.name, i, pp.tostring(e))) + end + end + return r +end + +------------------------------------------------------------------------------- +-- Parse a multisequence, without applying multisequence transformers. +-- The sequences are completely parsed. +------------------------------------------------------------------------------- +local function raw_parse_multisequence (lx, sequence_table, default) + local seq_parser = sequence_table[lx:is_keyword(lx:peek())] + if seq_parser then return seq_parser (lx) + elseif default then return default (lx) + else return false end +end + +------------------------------------------------------------------------------- +-- Applies all transformers listed in parser on ast. +------------------------------------------------------------------------------- +local function transform (ast, parser, fli, lli) + if parser.transformers then + for _, t in ipairs (parser.transformers) do ast = t(ast) or ast end + end + if type(ast) == 'table' then + local ali = ast.lineinfo + if not ali or ali.first~=fli or ali.last~=lli then + ast.lineinfo = lexer.new_lineinfo(fli, lli) + end + end + return ast +end + +------------------------------------------------------------------------------- +-- Generate a tracable parsing error (not implemented yet) +------------------------------------------------------------------------------- +function M.parse_error(lx, fmt, ...) + local li = lx:lineinfo_left() + local file, line, column, offset, positions + if li then + file, line, column, offset = li.source, li.line, li.column, li.offset + positions = { first = li, last = li } + else + line, column, offset = -1, -1, -1 + end + + local msg = string.format("line %i, char %i: "..fmt, line, column, ...) + if file and file~='?' then msg = "file "..file..", "..msg end + + local src = lx.src + if offset>0 and src then + local i, j = offset, offset + while src:sub(i,i) ~= '\n' and i>=0 do i=i-1 end + while src:sub(j,j) ~= '\n' and j<=#src do j=j+1 end + local srcline = src:sub (i+1, j-1) + local idx = string.rep (" ", column).."^" + msg = string.format("%s\n>>> %s\n>>> %s", msg, srcline, idx) + end + --lx :kill() + error(msg) +end + +------------------------------------------------------------------------------- +-- +-- Sequence parser generator +-- +------------------------------------------------------------------------------- +-- Input fields: +-- +-- * [builder]: how to build an AST out of sequence parts. let [x] be the list +-- of subparser results (keywords are simply omitted). [builder] can be: +-- - [nil], in which case the result of parsing is simply [x] +-- - a string, which is then put as a tag on [x] +-- - a function, which takes [x] as a parameter and returns an AST. +-- +-- * [name]: the name of the parser. Used for debug messages +-- +-- * [transformers]: a list of AST->AST functions, applied in order on ASTs +-- returned by the parser. +-- +-- * Table-part entries corresponds to keywords (strings) and subparsers +-- (function and callable objects). +-- +-- After creation, the following fields are added: +-- * [parse] the parsing function lexer->AST +-- * [kind] == "sequence" +-- * [name] is set, if it wasn't in the input. +-- +------------------------------------------------------------------------------- +function M.sequence (p) + M.make_parser ("sequence", p) + + ------------------------------------------------------------------- + -- Parsing method + ------------------------------------------------------------------- + function p:parse (lx) + + -- Raw parsing: + local fli = lx:lineinfo_right() + local seq = raw_parse_sequence (lx, self) + local lli = lx:lineinfo_left() + + -- Builder application: + local builder, tb = self.builder, type (self.builder) + if tb == "string" then seq.tag = builder + elseif tb == "function" or builder and builder.__call then seq = builder(seq) + elseif builder == nil then -- nothing + else error ("Invalid builder of type "..tb.." in sequence") end + seq = transform (seq, self, fli, lli) + assert (not seq or seq.lineinfo) + return seq + end + + ------------------------------------------------------------------- + -- Construction + ------------------------------------------------------------------- + -- Try to build a proper name + if p.name then + -- don't touch existing name + elseif type(p[1])=="string" then -- find name based on 1st keyword + if #p==1 then p.name=p[1] + elseif type(p[#p])=="string" then + p.name = p[1] .. " ... " .. p[#p] + else p.name = p[1] .. " ..." end + else -- can't find a decent name + p.name = "unnamed_sequence" + end + + return p +end -- + + +------------------------------------------------------------------------------- +-- +-- Multiple, keyword-driven, sequence parser generator +-- +------------------------------------------------------------------------------- +-- in [p], useful fields are: +-- +-- * [transformers]: as usual +-- +-- * [name]: as usual +-- +-- * Table-part entries must be sequence parsers, or tables which can +-- be turned into a sequence parser by [gg.sequence]. These +-- sequences must start with a keyword, and this initial keyword +-- must be different for each sequence. The table-part entries will +-- be removed after [gg.multisequence] returns. +-- +-- * [default]: the parser to run if the next keyword in the lexer is +-- none of the registered initial keywords. If there's no default +-- parser and no suitable initial keyword, the multisequence parser +-- simply returns [false]. +-- +-- After creation, the following fields are added: +-- +-- * [parse] the parsing function lexer->AST +-- +-- * [sequences] the table of sequences, indexed by initial keywords. +-- +-- * [add] method takes a sequence parser or a config table for +-- [gg.sequence], and adds/replaces the corresponding sequence +-- parser. If the keyword was already used, the former sequence is +-- removed and a warning is issued. +-- +-- * [get] method returns a sequence by its initial keyword +-- +-- * [kind] == "multisequence" +-- +------------------------------------------------------------------------------- +function M.multisequence (p) + M.make_parser ("multisequence", p) + + ------------------------------------------------------------------- + -- Add a sequence (might be just a config table for [gg.sequence]) + ------------------------------------------------------------------- + function p :add (s) + -- compile if necessary: + local keyword = type(s)=='table' and s[1] + if type(s)=='table' and not M.is_parser(s) then M.sequence(s) end + if M.is_parser(s)~='sequence' or type(keyword)~='string' then + if self.default then -- two defaults + error ("In a multisequence parser, all but one sequences ".. + "must start with a keyword") + else self.default = s end -- first default + else + if self.sequences[keyword] then -- duplicate keyword + -- TODO: warn that initial keyword `keyword` is overloaded in multiseq + end + self.sequences[keyword] = s + end + end -- + + ------------------------------------------------------------------- + -- Get the sequence starting with this keyword. [kw :: string] + ------------------------------------------------------------------- + function p :get (kw) return self.sequences [kw] end + + ------------------------------------------------------------------- + -- Remove the sequence starting with keyword [kw :: string] + ------------------------------------------------------------------- + function p :del (kw) + if not self.sequences[kw] then + -- TODO: warn that we try to delete a non-existent entry + end + local removed = self.sequences[kw] + self.sequences[kw] = nil + return removed + end + + ------------------------------------------------------------------- + -- Parsing method + ------------------------------------------------------------------- + function p :parse (lx) + local fli = lx:lineinfo_right() + local x = raw_parse_multisequence (lx, self.sequences, self.default) + local lli = lx:lineinfo_left() + return transform (x, self, fli, lli) + end + + ------------------------------------------------------------------- + -- Construction + ------------------------------------------------------------------- + -- Register the sequences passed to the constructor. They're going + -- from the array part of the parser to the hash part of field + -- [sequences] + p.sequences = { } + for i=1, #p do p :add (p[i]); p[i] = nil end + + -- FIXME: why is this commented out? + --if p.default and not is_parser(p.default) then sequence(p.default) end + return p +end -- + + +------------------------------------------------------------------------------- +-- +-- Expression parser generator +-- +------------------------------------------------------------------------------- +-- +-- Expression configuration relies on three tables: [prefix], [infix] +-- and [suffix]. Moreover, the primary parser can be replaced by a +-- table: in this case the [primary] table will be passed to +-- [gg.multisequence] to create a parser. +-- +-- Each of these tables is a modified multisequence parser: the +-- differences with respect to regular multisequence config tables are: +-- +-- * the builder takes specific parameters: +-- - for [prefix], it takes the result of the prefix sequence parser, +-- and the prefixed expression +-- - for [infix], it takes the left-hand-side expression, the results +-- of the infix sequence parser, and the right-hand-side expression. +-- - for [suffix], it takes the suffixed expression, and the result +-- of the suffix sequence parser. +-- +-- * the default field is a list, with parameters: +-- - [parser] the raw parsing function +-- - [transformers], as usual +-- - [prec], the operator's precedence +-- - [assoc] for [infix] table, the operator's associativity, which +-- can be "left", "right" or "flat" (default to left) +-- +-- In [p], useful fields are: +-- * [transformers]: as usual +-- * [name]: as usual +-- * [primary]: the atomic expression parser, or a multisequence config +-- table (mandatory) +-- * [prefix]: prefix operators config table, see above. +-- * [infix]: infix operators config table, see above. +-- * [suffix]: suffix operators config table, see above. +-- +-- After creation, these fields are added: +-- * [kind] == "expr" +-- * [parse] as usual +-- * each table is turned into a multisequence, and therefore has an +-- [add] method +-- +------------------------------------------------------------------------------- +function M.expr (p) + M.make_parser ("expr", p) + + ------------------------------------------------------------------- + -- parser method. + -- In addition to the lexer, it takes an optional precedence: + -- it won't read expressions whose precedence is lower or equal + -- to [prec]. + ------------------------------------------------------------------- + function p :parse (lx, prec) + prec = prec or 0 + + ------------------------------------------------------ + -- Extract the right parser and the corresponding + -- options table, for (pre|in|suff)fix operators. + -- Options include prec, assoc, transformers. + ------------------------------------------------------ + local function get_parser_info (tab) + local p2 = tab :get (lx :is_keyword (lx :peek())) + if p2 then -- keyword-based sequence found + local function parser(lx) return raw_parse_sequence(lx, p2) end + return parser, p2 + else -- Got to use the default parser + local d = tab.default + if d then return d.parse or d.parser, d + else return false, false end + end + end + + ------------------------------------------------------ + -- Look for a prefix sequence. Multiple prefixes are + -- handled through the recursive [p.parse] call. + -- Notice the double-transform: one for the primary + -- expr, and one for the one with the prefix op. + ------------------------------------------------------ + local function handle_prefix () + local fli = lx :lineinfo_right() + local p2_func, p2 = get_parser_info (self.prefix) + local op = p2_func and p2_func (lx) + if op then -- Keyword-based sequence found + local ili = lx :lineinfo_right() -- Intermediate LineInfo + local e = p2.builder (op, self :parse (lx, p2.prec)) + local lli = lx :lineinfo_left() + return transform (transform (e, p2, ili, lli), self, fli, lli) + else -- No prefix found, get a primary expression + local e = self.primary(lx) + local lli = lx :lineinfo_left() + return transform (e, self, fli, lli) + end + end -- + + ------------------------------------------------------ + -- Look for an infix sequence+right-hand-side operand. + -- Return the whole binary expression result, + -- or false if no operator was found. + ------------------------------------------------------ + local function handle_infix (e) + local p2_func, p2 = get_parser_info (self.infix) + if not p2 then return false end + + ----------------------------------------- + -- Handle flattening operators: gather all operands + -- of the series in [list]; when a different operator + -- is found, stop, build from [list], [transform] and + -- return. + ----------------------------------------- + if (not p2.prec or p2.prec>prec) and p2.assoc=="flat" then + local fli = lx:lineinfo_right() + local pflat, list = p2, { e } + repeat + local op = p2_func(lx) + if not op then break end + table.insert (list, self:parse (lx, p2.prec)) + local _ -- We only care about checking that p2==pflat + _, p2 = get_parser_info (self.infix) + until p2 ~= pflat + local e2 = pflat.builder (list) + local lli = lx:lineinfo_left() + return transform (transform (e2, pflat, fli, lli), self, fli, lli) + + ----------------------------------------- + -- Handle regular infix operators: [e] the LHS is known, + -- just gather the operator and [e2] the RHS. + -- Result goes in [e3]. + ----------------------------------------- + elseif p2.prec and p2.prec>prec or + p2.prec==prec and p2.assoc=="right" then + local fli = e.lineinfo.first -- lx:lineinfo_right() + local op = p2_func(lx) + if not op then return false end + local e2 = self:parse (lx, p2.prec) + local e3 = p2.builder (e, op, e2) + local lli = lx:lineinfo_left() + return transform (transform (e3, p2, fli, lli), self, fli, lli) + + ----------------------------------------- + -- Check for non-associative operators, and complain if applicable. + ----------------------------------------- + elseif p2.assoc=="none" and p2.prec==prec then + M.parse_error (lx, "non-associative operator!") + + ----------------------------------------- + -- No infix operator suitable at that precedence + ----------------------------------------- + else return false end + + end -- + + ------------------------------------------------------ + -- Look for a suffix sequence. + -- Return the result of suffix operator on [e], + -- or false if no operator was found. + ------------------------------------------------------ + local function handle_suffix (e) + -- FIXME bad fli, must take e.lineinfo.first + local p2_func, p2 = get_parser_info (self.suffix) + if not p2 then return false end + if not p2.prec or p2.prec>=prec then + --local fli = lx:lineinfo_right() + local fli = e.lineinfo.first + local op = p2_func(lx) + if not op then return false end + local lli = lx:lineinfo_left() + e = p2.builder (e, op) + e = transform (transform (e, p2, fli, lli), self, fli, lli) + return e + end + return false + end -- + + ------------------------------------------------------ + -- Parser body: read suffix and (infix+operand) + -- extensions as long as we're able to fetch more at + -- this precedence level. + ------------------------------------------------------ + local e = handle_prefix() + repeat + local x = handle_suffix (e); e = x or e + local y = handle_infix (e); e = y or e + until not (x or y) + + -- No transform: it already happened in operators handling + return e + end -- + + ------------------------------------------------------------------- + -- Construction + ------------------------------------------------------------------- + if not p.primary then p.primary=p[1]; p[1]=nil end + for _, t in ipairs{ "primary", "prefix", "infix", "suffix" } do + if not p[t] then p[t] = { } end + if not M.is_parser(p[t]) then M.multisequence(p[t]) end + end + function p:add(...) return self.primary:add(...) end + return p +end -- + + +------------------------------------------------------------------------------- +-- +-- List parser generator +-- +------------------------------------------------------------------------------- +-- In [p], the following fields can be provided in input: +-- +-- * [builder]: takes list of subparser results, returns AST +-- * [transformers]: as usual +-- * [name]: as usual +-- +-- * [terminators]: list of strings representing the keywords which +-- might mark the end of the list. When non-empty, the list is +-- allowed to be empty. A string is treated as a single-element +-- table, whose element is that string, e.g. ["do"] is the same as +-- [{"do"}]. +-- +-- * [separators]: list of strings representing the keywords which can +-- separate elements of the list. When non-empty, one of these +-- keyword has to be found between each element. Lack of a separator +-- indicates the end of the list. A string is treated as a +-- single-element table, whose element is that string, e.g. ["do"] +-- is the same as [{"do"}]. If [terminators] is empty/nil, then +-- [separators] has to be non-empty. +-- +-- After creation, the following fields are added: +-- * [parse] the parsing function lexer->AST +-- * [kind] == "list" +-- +------------------------------------------------------------------------------- +function M.list (p) + M.make_parser ("list", p) + + ------------------------------------------------------------------- + -- Parsing method + ------------------------------------------------------------------- + function p :parse (lx) + + ------------------------------------------------------ + -- Used to quickly check whether there's a terminator + -- or a separator immediately ahead + ------------------------------------------------------ + local function peek_is_in (keywords) + return keywords and lx:is_keyword(lx:peek(), unpack(keywords)) end + + local x = { } + local fli = lx :lineinfo_right() + + -- if there's a terminator to start with, don't bother trying + local is_empty_list = self.terminators and (peek_is_in (self.terminators) or lx:peek().tag=="Eof") + if not is_empty_list then + repeat + local item = self.primary(lx) + table.insert (x, item) -- read one element + until + -- There's a separator list specified, and next token isn't in it. + -- Otherwise, consume it with [lx:next()] + self.separators and not(peek_is_in (self.separators) and lx:next()) or + -- Terminator token ahead + peek_is_in (self.terminators) or + -- Last reason: end of file reached + lx:peek().tag=="Eof" + end + + local lli = lx:lineinfo_left() + + -- Apply the builder. It can be a string, or a callable value, + -- or simply nothing. + local b = self.builder + if b then + if type(b)=="string" then x.tag = b -- b is a string, use it as a tag + elseif type(b)=="function" then x=b(x) + else + local bmt = getmetatable(b) + if bmt and bmt.__call then x=b(x) end + end + end + return transform (x, self, fli, lli) + end -- + + ------------------------------------------------------------------- + -- Construction + ------------------------------------------------------------------- + if not p.primary then p.primary = p[1]; p[1] = nil end + if type(p.terminators) == "string" then p.terminators = { p.terminators } + elseif p.terminators and #p.terminators == 0 then p.terminators = nil end + if type(p.separators) == "string" then p.separators = { p.separators } + elseif p.separators and #p.separators == 0 then p.separators = nil end + + return p +end -- + + +------------------------------------------------------------------------------- +-- +-- Keyword-conditioned parser generator +-- +------------------------------------------------------------------------------- +-- +-- Only apply a parser if a given keyword is found. The result of +-- [gg.onkeyword] parser is the result of the subparser (modulo +-- [transformers] applications). +-- +-- lineinfo: the keyword is *not* included in the boundaries of the +-- resulting lineinfo. A review of all usages of gg.onkeyword() in the +-- implementation of metalua has shown that it was the appropriate choice +-- in every case. +-- +-- Input fields: +-- +-- * [name]: as usual +-- +-- * [transformers]: as usual +-- +-- * [peek]: if non-nil, the conditioning keyword is left in the lexeme +-- stream instead of being consumed. +-- +-- * [primary]: the subparser. +-- +-- * [keywords]: list of strings representing triggering keywords. +-- +-- * Table-part entries can contain strings, and/or exactly one parser. +-- Strings are put in [keywords], and the parser is put in [primary]. +-- +-- After the call, the following fields will be set: +-- +-- * [parse] the parsing method +-- * [kind] == "onkeyword" +-- * [primary] +-- * [keywords] +-- +------------------------------------------------------------------------------- +function M.onkeyword (p) + M.make_parser ("onkeyword", p) + + ------------------------------------------------------------------- + -- Parsing method + ------------------------------------------------------------------- + function p :parse (lx) + if lx :is_keyword (lx:peek(), unpack(self.keywords)) then + local fli = lx:lineinfo_right() + if not self.peek then lx:next() end + local content = self.primary (lx) + local lli = lx:lineinfo_left() + local li = content.lineinfo or { } + fli, lli = li.first or fli, li.last or lli + return transform (content, p, fli, lli) + else return false end + end + + ------------------------------------------------------------------- + -- Construction + ------------------------------------------------------------------- + if not p.keywords then p.keywords = { } end + for _, x in ipairs(p) do + if type(x)=="string" then table.insert (p.keywords, x) + else assert (not p.primary and M.is_parser (x)); p.primary = x end + end + assert (next (p.keywords), "Missing trigger keyword in gg.onkeyword") + assert (p.primary, 'no primary parser in gg.onkeyword') + return p +end -- + + +------------------------------------------------------------------------------- +-- +-- Optional keyword consummer pseudo-parser generator +-- +------------------------------------------------------------------------------- +-- +-- This doesn't return a real parser, just a function. That function parses +-- one of the keywords passed as parameters, and returns it. It returns +-- [false] if no matching keyword is found. +-- +-- Notice that tokens returned by lexer already carry lineinfo, therefore +-- there's no need to add them, as done usually through transform() calls. +------------------------------------------------------------------------------- +function M.optkeyword (...) + local args = {...} + if type (args[1]) == "table" then + assert (#args == 1) + args = args[1] + end + for _, v in ipairs(args) do assert (type(v)=="string") end + return function (lx) + local x = lx:is_keyword (lx:peek(), unpack (args)) + if x then lx:next(); return x + else return false end + end +end + + +------------------------------------------------------------------------------- +-- +-- Run a parser with a special lexer +-- +------------------------------------------------------------------------------- +-- +-- This doesn't return a real parser, just a function. +-- First argument is the lexer class to be used with the parser, +-- 2nd is the parser itself. +-- The resulting parser returns whatever the argument parser does. +-- +------------------------------------------------------------------------------- +function M.with_lexer(new_lexer, parser) + + ------------------------------------------------------------------- + -- Most gg functions take their parameters in a table, so it's + -- better to silently accept when with_lexer{ } is called with + -- its arguments in a list: + ------------------------------------------------------------------- + if not parser and #new_lexer==2 and type(new_lexer[1])=='table' then + return M.with_lexer(unpack(new_lexer)) + end + + ------------------------------------------------------------------- + -- Save the current lexer, switch it for the new one, run the parser, + -- restore the previous lexer, even if the parser caused an error. + ------------------------------------------------------------------- + return function (lx) + local old_lexer = getmetatable(lx) + lx:sync() + setmetatable(lx, new_lexer) + local status, result = pcall(parser, lx) + lx:sync() + setmetatable(lx, old_lexer) + if status then return result else error(result) end + end +end + +-------------------------------------------------------------------------------- +-- +-- Make sure a parser is used and returns successfully. +-- +-------------------------------------------------------------------------------- +function M.nonempty(primary) + local p = M.make_parser('non-empty list', { primary = primary, name=primary.name }) + function p :parse (lx) + local fli = lx:lineinfo_right() + local content = self.primary (lx) + local lli = lx:lineinfo_left() + local li = content.lineinfo or { } + fli, lli = li.first or fli, li.last or lli + if #content == 0 then + M.parse_error (lx, "`%s' must not be empty.", self.name or "list") + else + return transform (content, self, fli, lli) + end + end + return p +end + +local FUTURE_MT = { } +function FUTURE_MT:__tostring() return "" end +function FUTURE_MT:__newindex(key, value) error "don't write in futures" end +function FUTURE_MT :__index (parser_name) + return function(...) + local p, m = rawget(self, '__path'), self.__module + if p then for _, name in ipairs(p) do + m=rawget(m, name) + if not m then error ("Submodule '"..name.."' undefined") end + end end + local f = rawget(m, parser_name) + if not f then error ("Parser '"..parser_name.."' undefined") end + return f(...) + end +end + +function M.future(module, ...) + checks('table') + local path = ... and {...} + if path then for _, x in ipairs(path) do + assert(type(x)=='string', "Bad future arg") + end end + local self = { __module = module, + __path = path } + return setmetatable(self, FUTURE_MT) +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/grammar/lexer.lua b/Utils/luarocks/share/lua/5.1/metalua/grammar/lexer.lua new file mode 100644 index 000000000..0f96f8edf --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/grammar/lexer.lua @@ -0,0 +1,672 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +local checks = require 'checks' + +local M = { } + +local lexer = { alpha={ }, sym={ } } +lexer.__index=lexer +lexer.__type='lexer.stream' + +M.lexer = lexer + + +local debugf = function() end +-- local debugf=printf + +---------------------------------------------------------------------- +-- Some locale settings produce bad results, e.g. French locale +-- expect float numbers to use commas instead of periods. +-- TODO: change number parser into something loclae-independent, +-- locales are nasty. +---------------------------------------------------------------------- +os.setlocale('C') + +local MT = { } + +M.metatables=MT + +---------------------------------------------------------------------- +-- Create a new metatable, for a new class of objects. +---------------------------------------------------------------------- +local function new_metatable(name) + local mt = { __type = 'lexer.'..name }; + mt.__index = mt + MT[name] = mt +end + + +---------------------------------------------------------------------- +-- Position: represent a point in a source file. +---------------------------------------------------------------------- +new_metatable 'position' + +local position_idx=1 + +function M.new_position(line, column, offset, source) + checks('number', 'number', 'number', 'string') + local id = position_idx; position_idx = position_idx+1 + return setmetatable({line=line, column=column, offset=offset, + source=source, id=id}, MT.position) +end + +function MT.position :__tostring() + return string.format("<%s%s|L%d|C%d|K%d>", + self.comments and "C|" or "", + self.source, self.line, self.column, self.offset) +end + + + +---------------------------------------------------------------------- +-- Position factory: convert offsets into line/column/offset positions. +---------------------------------------------------------------------- +new_metatable 'position_factory' + +function M.new_position_factory(src, src_name) + -- assert(type(src)=='string') + -- assert(type(src_name)=='string') + local lines = { 1 } + for offset in src :gmatch '\n()' do table.insert(lines, offset) end + local max = #src+1 + table.insert(lines, max+1) -- +1 includes Eof + return setmetatable({ src_name=src_name, line2offset=lines, max=max }, + MT.position_factory) +end + +function MT.position_factory :get_position (offset) + -- assert(type(offset)=='number') + assert(offset<=self.max) + local line2offset = self.line2offset + local left = self.last_left or 1 + if offset", + fli.comments and "C|" or "", + fli.source, line, column, offset, + lli.comments and "|C" or "") +end + +---------------------------------------------------------------------- +-- Token: atomic Lua language element, with a category, a content, +-- and some lineinfo relating it to its original source. +---------------------------------------------------------------------- +new_metatable 'token' + +function M.new_token(tag, content, lineinfo) + --printf("TOKEN `%s{ %q, lineinfo = %s} boundaries %d, %d", + -- tag, content, tostring(lineinfo), lineinfo.first.id, lineinfo.last.id) + return setmetatable({tag=tag, lineinfo=lineinfo, content}, MT.token) +end + +function MT.token :__tostring() + --return string.format("`%s{ %q, %s }", self.tag, self[1], tostring(self.lineinfo)) + return string.format("`%s %q", self.tag, self[1]) +end + + +---------------------------------------------------------------------- +-- Comment: series of comment blocks with associated lineinfo. +-- To be attached to the tokens just before and just after them. +---------------------------------------------------------------------- +new_metatable 'comment' + +function M.new_comment(lines) + local first = lines[1].lineinfo.first + local last = lines[#lines].lineinfo.last + local lineinfo = M.new_lineinfo(first, last) + return setmetatable({lineinfo=lineinfo, unpack(lines)}, MT.comment) +end + +function MT.comment :text() + local last_line = self[1].lineinfo.last.line + local acc = { } + for i, line in ipairs(self) do + local nreturns = line.lineinfo.first.line - last_line + table.insert(acc, ("\n"):rep(nreturns)) + table.insert(acc, line[1]) + end + return table.concat(acc) +end + +function M.new_comment_line(text, lineinfo, nequals) + checks('string', 'lexer.lineinfo', '?number') + return { lineinfo = lineinfo, text, nequals } +end + + + +---------------------------------------------------------------------- +-- Patterns used by [lexer :extract] to decompose the raw string into +-- correctly tagged tokens. +---------------------------------------------------------------------- +lexer.patterns = { + spaces = "^[ \r\n\t]*()", + short_comment = "^%-%-([^\n]*)\n?()", + --final_short_comment = "^%-%-([^\n]*)()$", + long_comment = "^%-%-%[(=*)%[\n?(.-)%]%1%]()", + long_string = "^%[(=*)%[\n?(.-)%]%1%]()", + number_mantissa = { "^%d+%.?%d*()", "^%d*%.%d+()" }, + number_mantissa_hex = { "^%x+%.?%x*()", "^%x*%.%x+()" }, --Lua5.1 and Lua5.2 + number_exponant = "^[eE][%+%-]?%d+()", + number_exponant_hex = "^[pP][%+%-]?%d+()", --Lua5.2 + number_hex = "^0[xX]()", + word = "^([%a_][%w_]*)()" +} + +---------------------------------------------------------------------- +-- unescape a whole string, applying [unesc_digits] and +-- [unesc_letter] as many times as required. +---------------------------------------------------------------------- +local function unescape_string (s) + + -- Turn the digits of an escape sequence into the corresponding + -- character, e.g. [unesc_digits("123") == string.char(123)]. + local function unesc_digits (backslashes, digits) + if #backslashes%2==0 then + -- Even number of backslashes, they escape each other, not the digits. + -- Return them so that unesc_letter() can treat them + return backslashes..digits + else + -- Remove the odd backslash, which escapes the number sequence. + -- The rest will be returned and parsed by unesc_letter() + backslashes = backslashes :sub (1,-2) + end + local k, j, i = digits :reverse() :byte(1, 3) + local z = string.byte "0" + local code = (k or z) + 10*(j or z) + 100*(i or z) - 111*z + if code > 255 then + error ("Illegal escape sequence '\\"..digits.. + "' in string: ASCII codes must be in [0..255]") + end + local c = string.char (code) + if c == '\\' then c = '\\\\' end -- parsed by unesc_letter (test: "\092b" --> "\\b") + return backslashes..c + end + + -- Turn hex digits of escape sequence into char. + local function unesc_hex(backslashes, digits) + if #backslashes%2==0 then + return backslashes..'x'..digits + else + backslashes = backslashes :sub (1,-2) + end + local c = string.char(tonumber(digits,16)) + if c == '\\' then c = '\\\\' end -- parsed by unesc_letter (test: "\x5cb" --> "\\b") + return backslashes..c + end + + -- Handle Lua 5.2 \z sequences + local function unesc_z(backslashes, more) + if #backslashes%2==0 then + return backslashes..more + else + return backslashes :sub (1,-2) + end + end + + -- Take a letter [x], and returns the character represented by the + -- sequence ['\\'..x], e.g. [unesc_letter "n" == "\n"]. + local function unesc_letter(x) + local t = { + a = "\a", b = "\b", f = "\f", + n = "\n", r = "\r", t = "\t", v = "\v", + ["\\"] = "\\", ["'"] = "'", ['"'] = '"', ["\n"] = "\n" } + return t[x] or x + end + + s = s: gsub ("(\\+)(z%s*)", unesc_z) -- Lua 5.2 + s = s: gsub ("(\\+)([0-9][0-9]?[0-9]?)", unesc_digits) + s = s: gsub ("(\\+)x([0-9a-fA-F][0-9a-fA-F])", unesc_hex) -- Lua 5.2 + s = s: gsub ("\\(%D)",unesc_letter) + return s +end + +lexer.extractors = { + "extract_long_comment", "extract_short_comment", + "extract_short_string", "extract_word", "extract_number", + "extract_long_string", "extract_symbol" } + + + +---------------------------------------------------------------------- +-- Really extract next token from the raw string +-- (and update the index). +-- loc: offset of the position just after spaces and comments +-- previous_i: offset in src before extraction began +---------------------------------------------------------------------- +function lexer :extract () + local attached_comments = { } + local function gen_token(...) + local token = M.new_token(...) + if #attached_comments>0 then -- attach previous comments to token + local comments = M.new_comment(attached_comments) + token.lineinfo.first.comments = comments + if self.lineinfo_last_extracted then + self.lineinfo_last_extracted.comments = comments + end + attached_comments = { } + end + token.lineinfo.first.facing = self.lineinfo_last_extracted + self.lineinfo_last_extracted.facing = assert(token.lineinfo.first) + self.lineinfo_last_extracted = assert(token.lineinfo.last) + return token + end + while true do -- loop until a non-comment token is found + + -- skip whitespaces + self.i = self.src:match (self.patterns.spaces, self.i) + if self.i>#self.src then + local fli = self.posfact :get_position (#self.src+1) + local lli = self.posfact :get_position (#self.src+1) -- ok? + local tok = gen_token("Eof", "eof", M.new_lineinfo(fli, lli)) + tok.lineinfo.last.facing = lli + return tok + end + local i_first = self.i -- loc = position after whitespaces + + -- try every extractor until a token is found + for _, extractor in ipairs(self.extractors) do + local tag, content, xtra = self [extractor] (self) + if tag then + local fli = self.posfact :get_position (i_first) + local lli = self.posfact :get_position (self.i-1) + local lineinfo = M.new_lineinfo(fli, lli) + if tag=='Comment' then + local prev_comment = attached_comments[#attached_comments] + if not xtra -- new comment is short + and prev_comment and not prev_comment[2] -- prev comment is short + and prev_comment.lineinfo.last.line+1==fli.line then -- adjascent lines + -- concat with previous comment + prev_comment[1] = prev_comment[1].."\n"..content -- TODO quadratic, BAD! + prev_comment.lineinfo.last = lli + else -- accumulate comment + local comment = M.new_comment_line(content, lineinfo, xtra) + table.insert(attached_comments, comment) + end + break -- back to skipping spaces + else -- not a comment: real token, then + return gen_token(tag, content, lineinfo) + end -- if token is a comment + end -- if token found + end -- for each extractor + end -- while token is a comment +end -- :extract() + + + + +---------------------------------------------------------------------- +-- Extract a short comment. +---------------------------------------------------------------------- +function lexer :extract_short_comment() + -- TODO: handle final_short_comment + local content, j = self.src :match (self.patterns.short_comment, self.i) + if content then self.i=j; return 'Comment', content, nil end +end + +---------------------------------------------------------------------- +-- Extract a long comment. +---------------------------------------------------------------------- +function lexer :extract_long_comment() + local equals, content, j = self.src:match (self.patterns.long_comment, self.i) + if j then self.i = j; return "Comment", content, #equals end +end + +---------------------------------------------------------------------- +-- Extract a '...' or "..." short string. +---------------------------------------------------------------------- +function lexer :extract_short_string() + local k = self.src :sub (self.i,self.i) -- first char + if k~=[[']] and k~=[["]] then return end -- no match' + local i = self.i + 1 + local j = i + while true do + local x,y; x, j, y = self.src :match ("([\\\r\n"..k.."])()(.?)", j) -- next interesting char + if x == '\\' then + if y == 'z' then -- Lua 5.2 \z + j = self.src :match ("^%s*()", j+1) + else + j=j+1 -- escaped char + end + elseif x == k then break -- end of string + else + assert (not x or x=='\r' or x=='\n') + return nil, 'Unterminated string' + end + end + self.i = j + + return 'String', unescape_string (self.src :sub (i,j-2)) +end + +---------------------------------------------------------------------- +-- Extract Id or Keyword. +---------------------------------------------------------------------- +function lexer :extract_word() + local word, j = self.src:match (self.patterns.word, self.i) + if word then + self.i = j + return (self.alpha [word] and 'Keyword' or 'Id'), word + end +end + +---------------------------------------------------------------------- +-- Extract Number. +---------------------------------------------------------------------- +function lexer :extract_number() + local j = self.src:match(self.patterns.number_hex, self.i) + if j then + j = self.src:match (self.patterns.number_mantissa_hex[1], j) or + self.src:match (self.patterns.number_mantissa_hex[2], j) + if j then + j = self.src:match (self.patterns.number_exponant_hex, j) or j + end + else + j = self.src:match (self.patterns.number_mantissa[1], self.i) or + self.src:match (self.patterns.number_mantissa[2], self.i) + if j then + j = self.src:match (self.patterns.number_exponant, j) or j + end + end + if not j then return end + -- Number found, interpret with tonumber() and return it + local str = self.src:sub (self.i, j-1) + -- :TODO: tonumber on Lua5.2 floating hex may or may not work on Lua5.1 + local n = tonumber (str) + if not n then error(str.." is not a valid number according to tonumber()") end + self.i = j + return 'Number', n +end + +---------------------------------------------------------------------- +-- Extract long string. +---------------------------------------------------------------------- +function lexer :extract_long_string() + local _, content, j = self.src :match (self.patterns.long_string, self.i) + if j then self.i = j; return 'String', content end +end + +---------------------------------------------------------------------- +-- Extract symbol. +---------------------------------------------------------------------- +function lexer :extract_symbol() + local k = self.src:sub (self.i,self.i) + local symk = self.sym [k] -- symbols starting with `k` + if not symk then + self.i = self.i + 1 + return 'Keyword', k + end + for _, sym in pairs (symk) do + if sym == self.src:sub (self.i, self.i + #sym - 1) then + self.i = self.i + #sym + return 'Keyword', sym + end + end + self.i = self.i+1 + return 'Keyword', k +end + +---------------------------------------------------------------------- +-- Add a keyword to the list of keywords recognized by the lexer. +---------------------------------------------------------------------- +function lexer :add (w, ...) + assert(not ..., "lexer :add() takes only one arg, although possibly a table") + if type (w) == "table" then + for _, x in ipairs (w) do self :add (x) end + else + if w:match (self.patterns.word .. "$") then self.alpha [w] = true + elseif w:match "^%p%p+$" then + local k = w:sub(1,1) + local list = self.sym [k] + if not list then list = { }; self.sym [k] = list end + table.insert (list, w) + elseif w:match "^%p$" then return + else error "Invalid keyword" end + end +end + +---------------------------------------------------------------------- +-- Return the [n]th next token, without consuming it. +-- [n] defaults to 1. If it goes pass the end of the stream, an EOF +-- token is returned. +---------------------------------------------------------------------- +function lexer :peek (n) + if not n then n=1 end + if n > #self.peeked then + for i = #self.peeked+1, n do + self.peeked [i] = self :extract() + end + end + return self.peeked [n] +end + +---------------------------------------------------------------------- +-- Return the [n]th next token, removing it as well as the 0..n-1 +-- previous tokens. [n] defaults to 1. If it goes pass the end of the +-- stream, an EOF token is returned. +---------------------------------------------------------------------- +function lexer :next (n) + n = n or 1 + self :peek (n) + local a + for i=1,n do + a = table.remove (self.peeked, 1) + -- TODO: is this used anywhere? I think not. a.lineinfo.last may be nil. + --self.lastline = a.lineinfo.last.line + end + self.lineinfo_last_consumed = a.lineinfo.last + return a +end + +---------------------------------------------------------------------- +-- Returns an object which saves the stream's current state. +---------------------------------------------------------------------- +-- FIXME there are more fields than that to save +function lexer :save () return { self.i; {unpack(self.peeked) } } end + +---------------------------------------------------------------------- +-- Restore the stream's state, as saved by method [save]. +---------------------------------------------------------------------- +-- FIXME there are more fields than that to restore +function lexer :restore (s) self.i=s[1]; self.peeked=s[2] end + +---------------------------------------------------------------------- +-- Resynchronize: cancel any token in self.peeked, by emptying the +-- list and resetting the indexes +---------------------------------------------------------------------- +function lexer :sync() + local p1 = self.peeked[1] + if p1 then + local li_first = p1.lineinfo.first + if li_first.comments then li_first=li_first.comments.lineinfo.first end + self.i = li_first.offset + self.column_offset = self.i - li_first.column + self.peeked = { } + self.attached_comments = p1.lineinfo.first.comments or { } + end +end + +---------------------------------------------------------------------- +-- Take the source and offset of an old lexer. +---------------------------------------------------------------------- +function lexer :takeover(old) + self :sync(); old :sync() + for _, field in ipairs{ 'i', 'src', 'attached_comments', 'posfact' } do + self[field] = old[field] + end + return self +end + +---------------------------------------------------------------------- +-- Return the current position in the sources. This position is between +-- two tokens, and can be within a space / comment area, and therefore +-- have a non-null width. :lineinfo_left() returns the beginning of the +-- separation area, :lineinfo_right() returns the end of that area. +-- +-- ____ last consummed token ____ first unconsummed token +-- / / +-- XXXXX YYYYY +-- \____ \____ +-- :lineinfo_left() :lineinfo_right() +---------------------------------------------------------------------- +function lexer :lineinfo_right() + return self :peek(1).lineinfo.first +end + +function lexer :lineinfo_left() + return self.lineinfo_last_consumed +end + +---------------------------------------------------------------------- +-- Create a new lexstream. +---------------------------------------------------------------------- +function lexer :newstream (src_or_stream, name) + name = name or "?" + if type(src_or_stream)=='table' then -- it's a stream + return setmetatable ({ }, self) :takeover (src_or_stream) + elseif type(src_or_stream)=='string' then -- it's a source string + local src = src_or_stream + local pos1 = M.new_position(1, 1, 1, name) + local stream = { + src_name = name; -- Name of the file + src = src; -- The source, as a single string + peeked = { }; -- Already peeked, but not discarded yet, tokens + i = 1; -- Character offset in src + attached_comments = { },-- comments accumulator + lineinfo_last_extracted = pos1, + lineinfo_last_consumed = pos1, + posfact = M.new_position_factory (src_or_stream, name) + } + setmetatable (stream, self) + + -- Skip initial sharp-bang for Unix scripts + -- FIXME: redundant with mlp.chunk() + if src and src :match "^#!" then + local endofline = src :find "\n" + stream.i = endofline and (endofline + 1) or #src + end + return stream + else + assert(false, ":newstream() takes a source string or a stream, not a ".. + type(src_or_stream)) + end +end + +---------------------------------------------------------------------- +-- If there's no ... args, return the token a (whose truth value is +-- true) if it's a `Keyword{ }, or nil. If there are ... args, they +-- have to be strings. if the token a is a keyword, and it's content +-- is one of the ... args, then returns it (it's truth value is +-- true). If no a keyword or not in ..., return nil. +---------------------------------------------------------------------- +function lexer :is_keyword (a, ...) + if not a or a.tag ~= "Keyword" then return false end + local words = {...} + if #words == 0 then return a[1] end + for _, w in ipairs (words) do + if w == a[1] then return w end + end + return false +end + +---------------------------------------------------------------------- +-- Cause an error if the next token isn't a keyword whose content +-- is listed among ... args (which have to be strings). +---------------------------------------------------------------------- +function lexer :check (...) + local words = {...} + local a = self :next() + local function err () + error ("Got " .. tostring (a) .. + ", expected one of these keywords : '" .. + table.concat (words,"', '") .. "'") end + if not a or a.tag ~= "Keyword" then err () end + if #words == 0 then return a[1] end + for _, w in ipairs (words) do + if w == a[1] then return w end + end + err () +end + +---------------------------------------------------------------------- +-- +---------------------------------------------------------------------- +function lexer :clone() + local alpha_clone, sym_clone = { }, { } + for word in pairs(self.alpha) do alpha_clone[word]=true end + for letter, list in pairs(self.sym) do sym_clone[letter] = { unpack(list) } end + local clone = { alpha=alpha_clone, sym=sym_clone } + setmetatable(clone, self) + clone.__index = clone + return clone +end + +---------------------------------------------------------------------- +-- Cancel everything left in a lexer, all subsequent attempts at +-- `:peek()` or `:next()` will return `Eof`. +---------------------------------------------------------------------- +function lexer :kill() + self.i = #self.src+1 + self.peeked = { } + self.attached_comments = { } + self.lineinfo_last = self.posfact :get_position (#self.src+1) +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/loader.lua b/Utils/luarocks/share/lua/5.1/metalua/loader.lua new file mode 100644 index 000000000..e535fef32 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/loader.lua @@ -0,0 +1,133 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local M = require "package" -- extend Lua's basic "package" module +local checks = require 'checks' + +M.metalua_extension_prefix = 'metalua.extension.' + +-- Initialize package.mpath from package.path +M.mpath = M.mpath or os.getenv 'LUA_MPATH' or + (M.path..";") :gsub("%.(lua[:;])", ".m%1") :sub(1, -2) + +M.mcache = M.mcache or os.getenv 'LUA_MCACHE' + +---------------------------------------------------------------------- +-- resc(k) returns "%"..k if it's a special regular expression char, +-- or just k if it's normal. +---------------------------------------------------------------------- +local regexp_magic = { } +for k in ("^$()%.[]*+-?") :gmatch "." do regexp_magic[k]="%"..k end + +local function resc(k) return regexp_magic[k] or k end + +---------------------------------------------------------------------- +-- Take a Lua module name, return the open file and its name, +-- or and an error message. +---------------------------------------------------------------------- +function M.findfile(name, path_string) + local config_regexp = ("([^\n])\n"):rep(5):sub(1, -2) + local dir_sep, path_sep, path_mark, execdir, igmark = + M.config :match (config_regexp) + name = name:gsub ('%.', dir_sep) + local errors = { } + local path_pattern = string.format('[^%s]+', resc(path_sep)) + for path in path_string:gmatch (path_pattern) do + --printf('path = %s, rpath_mark=%s, name=%s', path, resc(path_mark), name) + local filename = path:gsub (resc (path_mark), name) + --printf('filename = %s', filename) + local file = io.open (filename, 'rb') + if file then return file, filename end + table.insert(errors, string.format("\tno file %q", filename)) + end + return false, '\n'..table.concat(errors, "\n")..'\n' +end + +---------------------------------------------------------------------- +-- Before compiling a metalua source module, try to find and load +-- a more recent bytecode dump. Requires lfs +---------------------------------------------------------------------- +local function metalua_cache_loader(name, src_filename, src) + if not M.mcache:find('%?') then + -- This is highly suspicious... + print("WARNING: no '?' character in $LUA_MCACHE/package.mcache") + end + local mlc = require 'metalua.compiler'.new() + local lfs = require 'lfs' + local dir_sep = M.config:sub(1,1) + local dst_filename = M.mcache :gsub ('%?', (name:gsub('%.', dir_sep))) + local src_a = lfs.attributes(src_filename) + local src_date = src_a and src_a.modification or 0 + local dst_a = lfs.attributes(dst_filename) + local dst_date = dst_a and dst_a.modification or 0 + local delta = dst_date - src_date + local bytecode, file, msg + if delta <= 0 then + --print ("(need to recompile "..src_filename.." into "..dst_filename..")") + bytecode = mlc :src_to_bytecode (src, '@'..src_filename) + for x in dst_filename :gmatch('()'..dir_sep) do + lfs.mkdir(dst_filename:sub(1,x)) + end + file, msg = io.open(dst_filename, 'wb') + if not file then error(msg) end + file :write (bytecode) + file :close() + else + file, msg = io.open(dst_filename, 'rb') + if not file then error(msg) end + bytecode = file :read '*a' + file :close() + end + return mlc :bytecode_to_function (bytecode, '@'..src_filename) +end + +---------------------------------------------------------------------- +-- Load a metalua source file. +---------------------------------------------------------------------- +function M.metalua_loader (name) + local file, filename_or_msg = M.findfile (name, M.mpath) + if not file then return filename_or_msg end + local luastring = file:read '*a' + file:close() + if M.mcache and pcall(require, 'lfs') then + return metalua_cache_loader(name, filename_or_msg, luastring) + else return require 'metalua.compiler'.new() :src_to_function (luastring, '@'..filename_or_msg) end +end + + +---------------------------------------------------------------------- +-- Placed after lua/luac loader, so precompiled files have +-- higher precedence. +---------------------------------------------------------------------- +table.insert(M.loaders, M.metalua_loader) + +---------------------------------------------------------------------- +-- Load an extension. +---------------------------------------------------------------------- +function extension (name, mlp) + local complete_name = M.metalua_extension_prefix..name + local extend_func = require (complete_name) + if not mlp.extensions[complete_name] then + local ast =extend_func(mlp) + mlp.extensions[complete_name] =extend_func + return ast + end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/pprint.lua b/Utils/luarocks/share/lua/5.1/metalua/pprint.lua new file mode 100644 index 000000000..73a842b63 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/pprint.lua @@ -0,0 +1,295 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +---------------------------------------------------------------------- + +---------------------------------------------------------------------- +---------------------------------------------------------------------- +-- +-- Lua objects pretty-printer +-- +---------------------------------------------------------------------- +---------------------------------------------------------------------- + +local M = { } + +M.DEFAULT_CFG = { + hide_hash = false; -- Print the non-array part of tables? + metalua_tag = true; -- Use Metalua's backtick syntax sugar? + fix_indent = nil; -- If a number, number of indentation spaces; + -- If false, indent to the previous brace. + line_max = nil; -- If a number, tries to avoid making lines with + -- more than this number of chars. + initial_indent = 0; -- If a number, starts at this level of indentation + keywords = { }; -- Set of keywords which must not use Lua's field + -- shortcuts {["foo"]=...} -> {foo=...} +} + +local function valid_id(cfg, x) + if type(x) ~= "string" then return false end + if not x:match "^[a-zA-Z_][a-zA-Z0-9_]*$" then return false end + if cfg.keywords and cfg.keywords[x] then return false end + return true +end + +local __tostring_cache = setmetatable({ }, {__mode='k'}) + +-- Retrieve the string produced by `__tostring` metamethod if present, +-- return `false` otherwise. Cached in `__tostring_cache`. +local function __tostring(x) + local the_string = __tostring_cache[x] + if the_string~=nil then return the_string end + local mt = getmetatable(x) + if mt then + local __tostring = mt.__tostring + if __tostring then + the_string = __tostring(x) + __tostring_cache[x] = the_string + return the_string + end + end + if x~=nil then __tostring_cache[x] = false end -- nil is an illegal key + return false +end + +local xlen -- mutually recursive with `xlen_type` + +local xlen_cache = setmetatable({ }, {__mode='k'}) + +-- Helpers for the `xlen` function +local xlen_type = { + ["nil"] = function ( ) return 3 end; + number = function (x) return #tostring(x) end; + boolean = function (x) return x and 4 or 5 end; + string = function (x) return #string.format("%q",x) end; +} + +function xlen_type.table (adt, cfg, nested) + local custom_string = __tostring(adt) + if custom_string then return #custom_string end + + -- Circular referenced objects are printed with the plain + -- `tostring` function in nested positions. + if nested [adt] then return #tostring(adt) end + nested [adt] = true + + local has_tag = cfg.metalua_tag and valid_id(cfg, adt.tag) + local alen = #adt + local has_arr = alen>0 + local has_hash = false + local x = 0 + + if not cfg.hide_hash then + -- first pass: count hash-part + for k, v in pairs(adt) do + if k=="tag" and has_tag then + -- this is the tag -> do nothing! + elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 and k>0 then + -- array-part pair -> do nothing! + else + has_hash = true + if valid_id(cfg, k) then x=x+#k + else x = x + xlen (k, cfg, nested) + 2 end -- count surrounding brackets + x = x + xlen (v, cfg, nested) + 5 -- count " = " and ", " + end + end + end + + for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", " + + nested[adt] = false -- No more nested calls + + if not (has_tag or has_arr or has_hash) then return 3 end + if has_tag then x=x+#adt.tag+1 end + if not (has_arr or has_hash) then return x end + if not has_hash and alen==1 and type(adt[1])~="table" then + return x-2 -- substract extraneous ", " + end + return x+2 -- count "{ " and " }", substract extraneous ", " +end + + +-- Compute the number of chars it would require to display the table +-- on a single line. Helps to decide whether some carriage returns are +-- required. Since the size of each sub-table is required many times, +-- it's cached in [xlen_cache]. +xlen = function (x, cfg, nested) + -- no need to compute length for 1-line prints + if not cfg.line_max then return 0 end + nested = nested or { } + if x==nil then return #"nil" end + local len = xlen_cache[x] + if len then return len end + local f = xlen_type[type(x)] + if not f then return #tostring(x) end + len = f (x, cfg, nested) + xlen_cache[x] = len + return len +end + +local function consider_newline(p, len) + if not p.cfg.line_max then return end + if p.current_offset + len <= p.cfg.line_max then return end + if p.indent < p.current_offset then + p:acc "\n"; p:acc ((" "):rep(p.indent)) + p.current_offset = p.indent + end +end + +local acc_value + +local acc_type = { + ["nil"] = function(p) p:acc("nil") end; + number = function(p, adt) p:acc (tostring (adt)) end; + string = function(p, adt) p:acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end; + boolean = function(p, adt) p:acc (adt and "true" or "false") end } + +-- Indentation: +-- * if `cfg.fix_indent` is set to a number: +-- * add this number of space for each level of depth +-- * return to the line as soon as it flushes things further left +-- * if not, tabulate to one space after the opening brace. +-- * as a result, it never saves right-space to return before first element + +function acc_type.table(p, adt) + if p.nested[adt] then p:acc(tostring(adt)); return end + p.nested[adt] = true + + local has_tag = p.cfg.metalua_tag and valid_id(p.cfg, adt.tag) + local alen = #adt + local has_arr = alen>0 + local has_hash = false + + local previous_indent = p.indent + + if has_tag then p:acc("`"); p:acc(adt.tag) end + + local function indent(p) + if not p.cfg.fix_indent then p.indent = p.current_offset + else p.indent = p.indent + p.cfg.fix_indent end + end + + -- First pass: handle hash-part + if not p.cfg.hide_hash then + for k, v in pairs(adt) do + + if has_tag and k=='tag' then -- pass the 'tag' field + elseif type(k)=="number" and k<=alen and k>0 and math.fmod(k,1)==0 then + -- pass array-part keys (consecutive ints less than `#adt`) + else -- hash-part keys + if has_hash then p:acc ", " else -- 1st hash-part pair ever found + p:acc "{ "; indent(p) + end + + -- Determine whether a newline is required + local is_id, expected_len=valid_id(p.cfg, k) + if is_id then expected_len=#k+xlen(v, p.cfg, p.nested)+#" = , " + else expected_len = xlen(k, p.cfg, p.nested)+xlen(v, p.cfg, p.nested)+#"[] = , " end + consider_newline(p, expected_len) + + -- Print the key + if is_id then p:acc(k); p:acc " = " else + p:acc "["; acc_value (p, k); p:acc "] = " + end + + acc_value (p, v) -- Print the value + has_hash = true + end + end + end + + -- Now we know whether there's a hash-part, an array-part, and a tag. + -- Tag and hash-part are already printed if they're present. + if not has_tag and not has_hash and not has_arr then p:acc "{ }"; + elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc + else + assert (has_hash or has_arr) -- special case { } already handled + local no_brace = false + if has_hash and has_arr then p:acc ", " + elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then + -- No brace required; don't print "{", remember not to print "}" + p:acc (" "); acc_value (p, adt[1]) -- indent= indent+(cfg.fix_indent or 0)) + no_brace = true + elseif not has_hash then + -- Braces required, but not opened by hash-part handler yet + p:acc "{ "; indent(p) + end + + -- 2nd pass: array-part + if not no_brace and has_arr then + local expected_len = xlen(adt[1], p.cfg, p.nested) + consider_newline(p, expected_len) + acc_value(p, adt[1]) -- indent+(cfg.fix_indent or 0) + for i=2, alen do + p:acc ", "; + consider_newline(p, xlen(adt[i], p.cfg, p.nested)) + acc_value (p, adt[i]) --indent+(cfg.fix_indent or 0) + end + end + if not no_brace then p:acc " }" end + end + p.nested[adt] = false -- No more nested calls + p.indent = previous_indent +end + + +function acc_value(p, v) + local custom_string = __tostring(v) + if custom_string then p:acc(custom_string) else + local f = acc_type[type(v)] + if f then f(p, v) else p:acc(tostring(v)) end + end +end + + +-- FIXME: new_indent seems to be always nil?!s detection +-- FIXME: accumulator function should be configurable, +-- so that print() doesn't need to bufferize the whole string +-- before starting to print. +function M.tostring(t, cfg) + + cfg = cfg or M.DEFAULT_CFG or { } + + local p = { + cfg = cfg; + indent = 0; + current_offset = cfg.initial_indent or 0; + buffer = { }; + nested = { }; + acc = function(self, str) + table.insert(self.buffer, str) + self.current_offset = self.current_offset + #str + end; + } + acc_value(p, t) + return table.concat(p.buffer) +end + +function M.print(...) return print(M.tostring(...)) end +function M.sprintf(fmt, ...) + local args={...} + for i, v in pairs(args) do + local t=type(v) + if t=='table' then args[i]=M.tostring(v) + elseif t=='nil' then args[i]='nil' end + end + return string.format(fmt, unpack(args)) +end + +function M.printf(...) print(M.sprintf(...)) end + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/repl.mlua b/Utils/luarocks/share/lua/5.1/metalua/repl.mlua new file mode 100644 index 000000000..0c89bc0b4 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/repl.mlua @@ -0,0 +1,108 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +-- Keep these global: +PRINT_AST = true +LINE_WIDTH = 60 +PROMPT = "M> " +PROMPT2 = ">> " + +local pp=require 'metalua.pprint' +local M = { } + +mlc = require 'metalua.compiler'.new() + +local readline + +do -- set readline() to a line reader, either editline otr a default + local status, editline = pcall(require, 'editline') + if status then + local rl_handle = editline.init 'metalua' + readline = |p| rl_handle:read(p) + else + local status, rl = pcall(require, 'readline') + if status then + rl.set_options{histfile='~/.metalua_history', keeplines=100, completion=false } + readline = rl.readline + else -- neither editline nor readline available + function readline (p) + io.write (p) + io.flush () + return io.read '*l' + end + end + end +end + +local function reached_eof(lx, msg) + return lx:peek().tag=='Eof' or msg:find "token `Eof" +end + + +function M.run() + pp.printf ("Metalua, interactive REPLoop.\n".. + "(c) 2006-2013 ") + local lines = { } + while true do + local src, lx, ast, f, results, success + repeat + local line = readline(next(lines) and PROMPT2 or PROMPT) + if not line then print(); os.exit(0) end -- line==nil iff eof on stdin + if not next(lines) then + line = line:gsub('^%s*=', 'return ') + end + table.insert(lines, line) + src = table.concat (lines, "\n") + until #line>0 + lx = mlc :src_to_lexstream(src) + success, ast = pcall(mlc.lexstream_to_ast, mlc, lx) + if success then + success, f = pcall(mlc.ast_to_function, mlc, ast, '=stdin') + if success then + results = { xpcall(f, debug.traceback) } + success = table.remove (results, 1) + if success then + -- Success! + for _, x in ipairs(results) do + pp.print(x, {line_max=LINE_WIDTH, metalua_tag=true}) + end + lines = { } + else + print "Evaluation error:" + print (results[1]) + lines = { } + end + else + print "Can't compile into bytecode:" + print (f) + lines = { } + end + else + -- If lx has been read entirely, try to read + -- another line before failing. + if not reached_eof(lx, ast) then + print "Can't compile source into AST:" + print (ast) + lines = { } + end + end + end +end + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/metalua/treequery.mlua b/Utils/luarocks/share/lua/5.1/metalua/treequery.mlua new file mode 100644 index 000000000..e369b9954 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/treequery.mlua @@ -0,0 +1,488 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +local walk = require 'metalua.treequery.walk' + +local M = { } +-- support for old-style modules +treequery = M + +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- +-- +-- multimap helper mmap: associate a key to a set of values +-- +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- + +local function mmap_add (mmap, node, x) + if node==nil then return false end + local set = mmap[node] + if set then set[x] = true + else mmap[node] = {[x]=true} end +end + +-- currently unused, I throw the whole set away +local function mmap_remove (mmap, node, x) + local set = mmap[node] + if not set then return false + elseif not set[x] then return false + elseif next(set) then set[x]=nil + else mmap[node] = nil end + return true +end + +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- +-- +-- TreeQuery object. +-- +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- + +local ACTIVE_SCOPE = setmetatable({ }, {__mode="k"}) + +-- treequery metatable +local Q = { }; Q.__index = Q + +--- treequery constructor +-- the resultingg object will allow to filter ans operate on the AST +-- @param root the AST to visit +-- @return a treequery visitor instance +function M.treequery(root) + return setmetatable({ + root = root, + unsatisfied = 0, + predicates = { }, + until_up = { }, + from_up = { }, + up_f = false, + down_f = false, + filters = { }, + }, Q) +end + +-- helper to share the implementations of positional filters +local function add_pos_filter(self, position, inverted, inclusive, f, ...) + if type(f)=='string' then f = M.has_tag(f, ...) end + if not inverted then self.unsatisfied += 1 end + local x = { + pred = f, + position = position, + satisfied = false, + inverted = inverted or false, + inclusive = inclusive or false } + table.insert(self.predicates, x) + return self +end + +function Q :if_unknown(f) + self.unknown_handler = f or (||nil) + return self +end + +-- TODO: offer an API for inclusive pos_filters + +--- select nodes which are after one which satisfies predicate f +Q.after = |self, f, ...| add_pos_filter(self, 'after', false, false, f, ...) +--- select nodes which are not after one which satisfies predicate f +Q.not_after = |self, f, ...| add_pos_filter(self, 'after', true, false, f, ...) +--- select nodes which are under one which satisfies predicate f +Q.under = |self, f, ...| add_pos_filter(self, 'under', false, false, f, ...) +--- select nodes which are not under one which satisfies predicate f +Q.not_under = |self, f, ...| add_pos_filter(self, 'under', true, false, f, ...) + +--- select nodes which satisfy predicate f +function Q :filter(f, ...) + if type(f)=='string' then f = M.has_tag(f, ...) end + table.insert(self.filters, f); + return self +end + +--- select nodes which satisfy predicate f +function Q :filter_not(f, ...) + if type(f)=='string' then f = M.has_tag(f, ...) end + table.insert(self.filters, |...| not f(...)) + return self +end + +-- private helper: apply filters and execute up/down callbacks when applicable +function Q :execute() + local cfg = { } + -- TODO: optimize away not_under & not_after by pruning the tree + function cfg.down(...) + --printf ("[down]\t%s\t%s", self.unsatisfied, table.tostring((...))) + ACTIVE_SCOPE[...] = cfg.scope + local satisfied = self.unsatisfied==0 + for _, x in ipairs(self.predicates) do + if not x.satisfied and x.pred(...) then + x.satisfied = true + local node, parent = ... + local inc = x.inverted and 1 or -1 + if x.position=='under' then + -- satisfied from after we get down this node... + self.unsatisfied += inc + -- ...until before we get up this node + mmap_add(self.until_up, node, x) + elseif x.position=='after' then + -- satisfied from after we get up this node... + mmap_add(self.from_up, node, x) + -- ...until before we get up this node's parent + mmap_add(self.until_up, parent, x) + elseif x.position=='under_or_after' then + -- satisfied from after we get down this node... + self.satisfied += inc + -- ...until before we get up this node's parent... + mmap_add(self.until_up, parent, x) + else + error "position not understood" + end -- position + if x.inclusive then satisfied = self.unsatisfied==0 end + end -- predicate passed + end -- for predicates + + if satisfied then + for _, f in ipairs(self.filters) do + if not f(...) then satisfied=false; break end + end + if satisfied and self.down_f then self.down_f(...) end + end + end + + function cfg.up(...) + --printf ("[up]\t%s", table.tostring((...))) + + -- Remove predicates which are due before we go up this node + local preds = self.until_up[...] + if preds then + for x, _ in pairs(preds) do + local inc = x.inverted and -1 or 1 + self.unsatisfied += inc + x.satisfied = false + end + self.until_up[...] = nil + end + + -- Execute the up callback + -- TODO: cache the filter passing result from the down callback + -- TODO: skip if there's no callback + local satisfied = self.unsatisfied==0 + if satisfied then + for _, f in ipairs(self.filters) do + if not f(self, ...) then satisfied=false; break end + end + if satisfied and self.up_f then self.up_f(...) end + end + + -- Set predicate which are due after we go up this node + local preds = self.from_up[...] + if preds then + for p, _ in pairs(preds) do + local inc = p.inverted and 1 or -1 + self.unsatisfied += inc + end + self.from_up[...] = nil + end + ACTIVE_SCOPE[...] = nil + end + + function cfg.binder(id_node, ...) + --printf(" >>> Binder called on %s, %s", table.tostring(id_node), + -- table.tostring{...}:sub(2,-2)) + cfg.down(id_node, ...) + cfg.up(id_node, ...) + --printf("down/up on binder done") + end + + cfg.unknown = self.unknown_handler + + --function cfg.occurrence (binder, occ) + -- if binder then OCC2BIND[occ] = binder[1] end + --printf(" >>> %s is an occurrence of %s", occ[1], table.tostring(binder and binder[2])) + --end + + --function cfg.binder(...) cfg.down(...); cfg.up(...) end + return walk.guess(cfg, self.root) +end + +--- Execute a function on each selected node +-- @down: function executed when we go down a node, i.e. before its children +-- have been examined. +-- @up: function executed when we go up a node, i.e. after its children +-- have been examined. +function Q :foreach(down, up) + if not up and not down then + error "iterator missing" + end + self.up_f = up + self.down_f = down + return self :execute() +end + +--- Return the list of nodes selected by a given treequery. +function Q :list() + local acc = { } + self :foreach(|x| table.insert(acc, x)) + return acc +end + +--- Return the first matching element +-- TODO: dirty hack, to implement properly with a 'break' return. +-- Also, it won't behave correctly if a predicate causes an error, +-- or if coroutines are involved. +function Q :first() + local result = { } + local function f(...) result = {...}; error() end + pcall(|| self :foreach(f)) + return unpack(result) +end + +--- Pretty printer for queries +function Q :__tostring() return "" end + +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- +-- +-- Predicates. +-- +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- + +--- Return a predicate which is true if the tested node's tag is among the +-- one listed as arguments +-- @param ... a sequence of tag names +function M.has_tag(...) + local args = {...} + if #args==1 then + local tag = ... + return (|node| node.tag==tag) + --return function(self, node) printf("node %s has_tag %s?", table.tostring(node), tag); return node.tag==tag end + else + local tags = { } + for _, tag in ipairs(args) do tags[tag]=true end + return function(node) + local node_tag = node.tag + return node_tag and tags[node_tag] + end + end +end + +--- Predicate to test whether a node represents an expression. +M.is_expr = M.has_tag('Nil', 'Dots', 'True', 'False', 'Number','String', + 'Function', 'Table', 'Op', 'Paren', 'Call', 'Invoke', + 'Id', 'Index') + +-- helper for is_stat +local STAT_TAGS = { Do=1, Set=1, While=1, Repeat=1, If=1, Fornum=1, + Forin=1, Local=1, Localrec=1, Return=1, Break=1 } + +--- Predicate to test whether a node represents a statement. +-- It is context-aware, i.e. it recognizes `Call and `Invoke nodes +-- used in a statement context as such. +function M.is_stat(node, parent) + local tag = node.tag + if not tag then return false + elseif STAT_TAGS[tag] then return true + elseif tag=='Call' or tag=='Invoke' then return parent and parent.tag==nil + else return false end +end + +--- Predicate to test whether a node represents a statements block. +function M.is_block(node) return node.tag==nil end + +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- +-- +-- Variables and scopes. +-- +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- + +local BINDER_PARENT_TAG = { + Local=true, Localrec=true, Forin=true, Function=true } + +--- Test whether a node is a binder. This is local predicate, although it +-- might need to inspect the parent node. +function M.is_binder(node, parent) + --printf('is_binder(%s, %s)', table.tostring(node), table.tostring(parent)) + if node.tag ~= 'Id' or not parent then return false end + if parent.tag=='Fornum' then return parent[1]==node end + if not BINDER_PARENT_TAG[parent.tag] then return false end + for _, binder in ipairs(parent[1]) do + if binder==node then return true end + end + return false +end + +--- Retrieve the binder associated to an occurrence within root. +-- @param occurrence an Id node representing an occurrence in `root`. +-- @param root the tree in which `node` and its binder occur. +-- @return the binder node, and its ancestors up to root if found. +-- @return nil if node is global (or not an occurrence) in `root`. +function M.binder(occurrence, root) + local cfg, id_name, result = { }, occurrence[1], { } + function cfg.occurrence(id) + if id == occurrence then result = cfg.scope :get(id_name) end + -- TODO: break the walker + end + walk.guess(cfg, root) + return unpack(result) +end + +--- Predicate to filter occurrences of a given binder. +-- Warning: it relies on internal scope book-keeping, +-- and for this reason, it only works as query method argument. +-- It won't work outside of a query. +-- @param binder the binder whose occurrences must be kept by predicate +-- @return a predicate + +-- function M.is_occurrence_of(binder) +-- return function(node, ...) +-- if node.tag ~= 'Id' then return nil end +-- if M.is_binder(node, ...) then return nil end +-- local scope = ACTIVE_SCOPE[node] +-- if not scope then return nil end +-- local result = scope :get (node[1]) or { } +-- if result[1] ~= binder then return nil end +-- return unpack(result) +-- end +-- end + +function M.is_occurrence_of(binder) + return function(node, ...) + local b = M.get_binder(node) + return b and b==binder + end +end + +function M.get_binder(occurrence, ...) + if occurrence.tag ~= 'Id' then return nil end + if M.is_binder(occurrence, ...) then return nil end + local scope = ACTIVE_SCOPE[occurrence] + local binder_hierarchy = scope :get(occurrence[1]) + return unpack (binder_hierarchy or { }) +end + +--- Transform a predicate on a node into a predicate on this node's +-- parent. For instance if p tests whether a node has property P, +-- then parent(p) tests whether this node's parent has property P. +-- The ancestor level is precised with n, with 1 being the node itself, +-- 2 its parent, 3 its grand-parent etc. +-- @param[optional] n the parent to examine, default=2 +-- @param pred the predicate to transform +-- @return a predicate +function M.parent(n, pred, ...) + if type(n)~='number' then n, pred = 2, n end + if type(pred)=='string' then pred = M.has_tag(pred, ...) end + return function(self, ...) + return select(n, ...) and pred(self, select(n, ...)) + end +end + +--- Transform a predicate on a node into a predicate on this node's +-- n-th child. +-- @param n the child's index number +-- @param pred the predicate to transform +-- @return a predicate +function M.child(n, pred) + return function(node, ...) + local child = node[n] + return child and pred(child, node, ...) + end +end + +--- Predicate to test the position of a node in its parent. +-- The predicate succeeds if the node is the n-th child of its parent, +-- and a <= n <= b. +-- nth(a) is equivalent to nth(a, a). +-- Negative indices are admitted, and count from the last child, +-- as done for instance by string.sub(). +-- +-- TODO: This is wrong, this tests the table relationship rather than the +-- AST node relationship. +-- Must build a getindex helper, based on pattern matching, then build +-- the predicate around it. +-- +-- @param a lower bound +-- @param a upper bound +-- @return a predicate +function M.is_nth(a, b) + b = b or a + return function(self, node, parent) + if not parent then return false end + local nchildren = #parent + local a = a<=0 and nchildren+a+1 or a + if a>nchildren then return false end + local b = b<=0 and nchildren+b+1 or b>nchildren and nchildren or b + for i=a,b do if parent[i]==node then return true end end + return false + end +end + +--- Returns a list of the direct children of AST node `ast`. +-- Children are only expressions, statements and blocks, +-- not intermediates such as `Pair` nodes or internal lists +-- in `Local` or `Set` nodes. +-- Children are returned in parsing order, which isn't necessarily +-- the same as source code order. For instance, the right-hand-side +-- of a `Local` node is listed before the left-hand-side, because +-- semantically the right is evaluated before the variables on the +-- left enter scope. +-- +-- @param ast the node whose children are needed +-- @return a list of the direct children of `ast` +function M.children(ast) + local acc = { } + local cfg = { } + function cfg.down(x) + if x~=ast then table.insert(acc, x); return 'break' end + end + walk.guess(cfg, ast) + return acc +end + +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- +-- +-- Comments parsing. +-- +-- ----------------------------------------------------------------------------- +-- ----------------------------------------------------------------------------- + +local comment_extractor = |which_side| function (node) + local x = node.lineinfo + x = x and x[which_side] + x = x and x.comments + if not x then return nil end + local lines = { } + for _, record in ipairs(x) do + table.insert(lines, record[1]) + end + return table.concat(lines, '\n') +end + +M.comment_prefix = comment_extractor 'first' +M.comment_suffix = comment_extractor 'last' + + +--- Shortcut for the query constructor +function M :__call(...) return self.treequery(...) end +setmetatable(M, M) + +return M diff --git a/Utils/luarocks/share/lua/5.1/metalua/treequery/walk.mlua b/Utils/luarocks/share/lua/5.1/metalua/treequery/walk.mlua new file mode 100644 index 000000000..94fc5d65d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/metalua/treequery/walk.mlua @@ -0,0 +1,266 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- 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 +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +-- Low level AST traversal library. +-- +-- This library is a helper for the higher-level `treequery` library. +-- It walks through every node of an AST, depth-first, and executes +-- some callbacks contained in its `cfg` config table: +-- +-- * `cfg.down(...)` is called when it walks down a node, and receive as +-- parameters the node just entered, followed by its parent, grand-parent +-- etc. until the root node. +-- +-- * `cfg.up(...)` is called when it walks back up a node, and receive as +-- parameters the node just entered, followed by its parent, grand-parent +-- etc. until the root node. +-- +-- * `cfg.occurrence(binder, id_node, ...)` is called when it visits +-- an `` `Id{ }`` node which isn't a local variable creator. binder +-- is a reference to its binder with its context. The binder is the +-- `` `Id{ }`` node which created this local variable. By "binder +-- and its context", we mean a list starting with the `` `Id{ }``, +-- and followed by every ancestor of the binder node, up until the +-- common root node. `binder` is nil if the variable is global. +-- `id_node` is followed by its ancestor, up until the root node. +-- +-- `cfg.scope` is maintained during the traversal, associating a +-- variable name to the binder which creates it in the context of the +-- node currently visited. +-- +-- `walk.traverse.xxx` functions are in charge of the recursive +-- descent into children nodes. They're private helpers. They are also +-- in charge of calling appropriate `cfg.xxx` callbacks. + +-{ extension ("match", ...) } + +local pp = require 'metalua.pprint' + +local M = { traverse = { }; tags = { }; debug = false } + +local function table_transpose(t) + local tt = { }; for a, b in pairs(t) do tt[b]=a end; return tt +end + +-------------------------------------------------------------------------------- +-- Standard tags: can be used to guess the type of an AST, or to check +-- that the type of an AST is respected. +-------------------------------------------------------------------------------- +M.tags.stat = table_transpose{ + 'Do', 'Set', 'While', 'Repeat', 'Local', 'Localrec', 'Return', + 'Fornum', 'Forin', 'If', 'Break', 'Goto', 'Label', + 'Call', 'Invoke' } +M.tags.expr = table_transpose{ + 'Paren', 'Call', 'Invoke', 'Index', 'Op', 'Function', 'Stat', + 'Table', 'Nil', 'Dots', 'True', 'False', 'Number', 'String', 'Id' } + +-------------------------------------------------------------------------------- +-- These [M.traverse.xxx()] functions are in charge of actually going through +-- ASTs. At each node, they make sure to call the appropriate walker. +-------------------------------------------------------------------------------- + +function M.traverse.stat (cfg, x, ...) + if M.debug then pp.printf("traverse stat %s", x) end + local ancestors = {...} + local B = |y| M.block (cfg, y, x, unpack(ancestors)) -- Block + local S = |y| M.stat (cfg, y, x, unpack(ancestors)) -- Statement + local E = |y| M.expr (cfg, y, x, unpack(ancestors)) -- Expression + local EL = |y| M.expr_list (cfg, y, x, unpack(ancestors)) -- Expression List + local IL = |y| M.binder_list (cfg, y, x, unpack(ancestors)) -- Id binders List + local OS = || cfg.scope :save() -- Open scope + local CS = || cfg.scope :restore() -- Close scope + + match x with + | {...} if x.tag == nil -> for _, y in ipairs(x) do M.stat(cfg, y, ...) end + -- no tag --> node not inserted in the history ancestors + | `Do{...} -> OS(x); for _, y in ipairs(x) do S(y) end; CS(x) + | `Set{ lhs, rhs } -> EL(lhs); EL(rhs) + | `While{ cond, body } -> E(cond); OS(); B(body); CS() + | `Repeat{ body, cond } -> OS(body); B(body); E(cond); CS(body) + | `Local{ lhs } -> IL(lhs) + | `Local{ lhs, rhs } -> EL(rhs); IL(lhs) + | `Localrec{ lhs, rhs } -> IL(lhs); EL(rhs) + | `Fornum{ i, a, b, body } -> E(a); E(b); OS(); IL{i}; B(body); CS() + | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); OS(); IL{i}; B(body); CS() + | `Forin{ i, rhs, body } -> EL(rhs); OS(); IL(i); B(body); CS() + | `If{...} -> + for i=1, #x-1, 2 do + E(x[i]); OS(); B(x[i+1]); CS() + end + if #x%2 == 1 then + OS(); B(x[#x]); CS() + end + | `Call{...}|`Invoke{...}|`Return{...} -> EL(x) + | `Break | `Goto{ _ } | `Label{ _ } -> -- nothing + | { tag=tag, ...} if M.tags.stat[tag]-> + M.malformed (cfg, x, unpack (ancestors)) + | _ -> + M.unknown (cfg, x, unpack (ancestors)) + end +end + +function M.traverse.expr (cfg, x, ...) + if M.debug then pp.printf("traverse expr %s", x) end + local ancestors = {...} + local B = |y| M.block (cfg, y, x, unpack(ancestors)) -- Block + local S = |y| M.stat (cfg, y, x, unpack(ancestors)) -- Statement + local E = |y| M.expr (cfg, y, x, unpack(ancestors)) -- Expression + local EL = |y| M.expr_list (cfg, y, x, unpack(ancestors)) -- Expression List + local IL = |y| M.binder_list (cfg, y, x, unpack(ancestors)) -- Id binders list + local OS = || cfg.scope :save() -- Open scope + local CS = || cfg.scope :restore() -- Close scope + + match x with + | `Paren{ e } -> E(e) + | `Call{...} | `Invoke{...} -> EL(x) + | `Index{ a, b } -> E(a); E(b) + | `Op{ opid, ... } -> E(x[2]); if #x==3 then E(x[3]) end + | `Function{ params, body } -> OS(body); IL(params); B(body); CS(body) + | `Stat{ b, e } -> OS(b); B(b); E(e); CS(b) + | `Id{ name } -> M.occurrence(cfg, x, unpack(ancestors)) + | `Table{ ... } -> + for i = 1, #x do match x[i] with + | `Pair{ k, v } -> E(k); E(v) + | v -> E(v) + end end + | `Nil|`Dots|`True|`False|`Number{_}|`String{_} -> -- terminal node + | { tag=tag, ...} if M.tags.expr[tag]-> M.malformed (cfg, x, unpack (ancestors)) + | _ -> M.unknown (cfg, x, unpack (ancestors)) + end +end + +function M.traverse.block (cfg, x, ...) + assert(type(x)=='table', "traverse.block() expects a table") + if x.tag then M.malformed(cfg, x, ...) + else for _, y in ipairs(x) do M.stat(cfg, y, x, ...) end + end +end + +function M.traverse.expr_list (cfg, x, ...) + assert(type(x)=='table', "traverse.expr_list() expects a table") + -- x doesn't appear in the ancestors + for _, y in ipairs(x) do M.expr(cfg, y, ...) end +end + +function M.malformed(cfg, x, ...) + local f = cfg.malformed or cfg.error + if f then f(x, ...) else + error ("Malformed node of tag "..(x.tag or '(nil)')) + end +end + +function M.unknown(cfg, x, ...) + local f = cfg.unknown or cfg.error + if f then f(x, ...) else + error ("Unknown node tag "..(x.tag or '(nil)')) + end +end + +function M.occurrence(cfg, x, ...) + if cfg.occurrence then cfg.occurrence(cfg.scope :get(x[1]), x, ...) end +end + +-- TODO: Is it useful to call each error handling function? +function M.binder_list (cfg, id_list, ...) + local f = cfg.binder + local ferror = cfg.error or cfg.malformed or cfg.unknown + for i, id_node in ipairs(id_list) do + local down, up = cfg.down, cfg.up + if id_node.tag == 'Id' then + cfg.scope :set (id_node[1], { id_node, ... }) + if down then down(id_node, ...) end + if f then f(id_node, ...) end + if up then up(id_node, ...) end + elseif i==#id_list and id_node.tag=='Dots' then + if down then down(id_node, ...) end + if up then up(id_node, ...) end + -- Do nothing, those are valid `Dots + elseif ferror then + -- Traverse error handling function + ferror(id_node, ...) + else + error("Invalid binders list") + end + end +end + +---------------------------------------------------------------------- +-- Generic walker generator. +-- * if `cfg' has an entry matching the tree name, use this entry +-- * if not, try to use the entry whose name matched the ast kind +-- * if an entry is a table, look for 'up' and 'down' entries +-- * if it is a function, consider it as a `down' traverser. +---------------------------------------------------------------------- +local walker_builder = function(traverse) + assert(traverse) + return function (cfg, ...) + if not cfg.scope then cfg.scope = M.newscope() end + local down, up = cfg.down, cfg.up + local broken = down and down(...) + if broken ~= 'break' then M.traverse[traverse] (cfg, ...) end + if up then up(...) end + end +end + +---------------------------------------------------------------------- +-- Declare [M.stat], [M.expr], [M.block]. +-- `M.binder_list` is not here, because `cfg.up` and `cfg.down` must +-- be called on individual binders, not on the list itself. +-- It's therefore handled in `traverse.binder_list()` +---------------------------------------------------------------------- +for _, w in ipairs{ "stat", "expr", "block" } do --, "malformed", "unknown" } do + M[w] = walker_builder (w, M.traverse[w]) +end + +-- Don't call up/down callbacks on expr lists +M.expr_list = M.traverse.expr_list + + +---------------------------------------------------------------------- +-- Try to guess the type of the AST then choose the right walkker. +---------------------------------------------------------------------- +function M.guess (cfg, x, ...) + assert(type(x)=='table', "arg #2 in a walker must be an AST") + if M.tags.expr[x.tag] then return M.expr(cfg, x, ...) end + if M.tags.stat[x.tag] then return M.stat(cfg, x, ...) end + if not x.tag then return M.block(cfg, x, ...) end + error ("Can't guess the AST type from tag "..(x.tag or '')) +end + +local S = { }; S.__index = S + +function M.newscope() + local instance = { current = { } } + instance.stack = { instance.current } + setmetatable (instance, S) + return instance +end + +function S :save(...) + local current_copy = { } + for a, b in pairs(self.current) do current_copy[a]=b end + table.insert (self.stack, current_copy) + if ... then return self :add(...) end +end + +function S :restore() self.current = table.remove (self.stack) end +function S :get (var_name) return self.current[var_name] end +function S :set (key, val) self.current[key] = val end + +return M diff --git a/Utils/luarocks/share/lua/5.1/models/apimodel.lua b/Utils/luarocks/share/lua/5.1/models/apimodel.lua new file mode 100644 index 000000000..e0ed1c968 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/models/apimodel.lua @@ -0,0 +1,241 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2011-2012 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: +-- Simon BERNARD +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local M = {} + +-------------------------------------------------------------------------------- +-- API MODEL +-------------------------------------------------------------------------------- + +function M._file() + local file = { + -- FIELDS + tag = "file", + name = nil, -- string + shortdescription = "", -- string + description = "", -- string + types = {}, -- map from typename to type + globalvars = {}, -- map from varname to item + returns = {}, -- list of return + + -- FUNCTIONS + addtype = function (self,type) + self.types[type.name] = type + type.parent = self + end, + + mergetype = function (self,newtype,erase,erasesourcerangefield) + local currenttype = self.types[newtype.name] + if currenttype then + -- merge recordtypedef + if currenttype.tag =="recordtypedef" and newtype.tag == "recordtypedef" then + -- merge fields + for fieldname ,field in pairs( newtype.fields) do + local currentfield = currenttype.fields[fieldname] + if erase or not currentfield then + currenttype:addfield(field) + elseif erasesourcerangefield then + if field.sourcerange.min and field.sourcerange.max then + currentfield.sourcerange.min = field.sourcerange.min + currentfield.sourcerange.max = field.sourcerange.max + end + end + end + + -- merge descriptions and source ranges + if erase then + if newtype.description or newtype.description == "" then currenttype.description = newtype.description end + if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end + if newtype.sourcerange.min and newtype.sourcerange.max then + currenttype.sourcerange.min = newtype.sourcerange.min + currenttype.sourcerange.max = newtype.sourcerange.max + end + end + -- merge functiontypedef + elseif currenttype.tag == "functiontypedef" and newtype.tag == "functiontypedef" then + -- merge params + for i, param1 in ipairs(newtype.params) do + local missing = true + for j, param2 in ipairs(currenttype.params) do + if param1.name == param2.name then + missing = false + break + end + end + if missing then + table.insert(currenttype.params,param1) + end + end + + -- merge descriptions and source ranges + if erase then + if newtype.description or newtype.description == "" then currenttype.description = newtype.description end + if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end + if newtype.sourcerange.min and newtype.sourcerange.max then + currenttype.sourcerange.min = newtype.sourcerange.min + currenttype.sourcerange.max = newtype.sourcerange.max + end + end + end + else + self:addtype(newtype) + end + end, + + addglobalvar = function (self,item) + self.globalvars[item.name] = item + item.parent = self + end, + + moduletyperef = function (self) + if self and self.returns[1] and self.returns[1].types[1] then + local typeref = self.returns[1].types[1] + return typeref + end + end + } + return file +end + +function M._recordtypedef(name) + local recordtype = { + -- FIELDS + tag = "recordtypedef", + name = name, -- string (mandatory) + shortdescription = "", -- string + description = "", -- string + fields = {}, -- map from fieldname to field + sourcerange = {min=0,max=0}, + + -- FUNCTIONS + addfield = function (self,field) + self.fields[field.name] = field + field.parent = self + end + } + return recordtype +end + +function M._functiontypedef(name) + return { + tag = "functiontypedef", + name = name, -- string (mandatory) + shortdescription = "", -- string + description = "", -- string + params = {}, -- list of parameter + returns = {} -- list of return + } +end + +function M._parameter(name) + return { + tag = "parameter", + name = name, -- string (mandatory) + description = "", -- string + type = nil -- typeref (external or internal or primitive typeref) + } +end + +function M._item(name) + return { + -- FIELDS + tag = "item", + name = name, -- string (mandatory) + shortdescription = "", -- string + description = "", -- string + type = nil, -- typeref (external or internal or primitive typeref) + occurrences = {}, -- list of identifier (see internalmodel) + sourcerange = {min=0, max=0}, + + -- This is A TRICK + -- This value is ALWAYS nil, except for internal purposes (short references). + external = nil, + + -- FUNCTIONS + addoccurence = function (self,occ) + table.insert(self.occurrences,occ) + occ.definition = self + end, + + resolvetype = function (self,file) + if self and self.type then + if self.type.tag =="internaltyperef" then + -- if file is not given try to retrieve it. + if not file then + if self.parent and self.parent.tag == 'recordtypedef' then + file = self.parent.parent + elseif self.parent.tag == 'file' then + file = self.parent + end + end + if file then return file.types[self.type.typename] end + elseif self.type.tag =="inlinetyperef" then + return self.type.def + end + end + end + } +end + +function M._externaltypref(modulename, typename) + return { + tag = "externaltyperef", + modulename = modulename, -- string + typename = typename -- string + } +end + +function M._internaltyperef(typename) + return { + tag = "internaltyperef", + typename = typename -- string + } +end + +function M._primitivetyperef(typename) + return { + tag = "primitivetyperef", + typename = typename -- string + } +end + +function M._moduletyperef(modulename,returnposition) + return { + tag = "moduletyperef", + modulename = modulename, -- string + returnposition = returnposition -- number + } +end + +function M._exprtyperef(expression,returnposition) + return { + tag = "exprtyperef", + expression = expression, -- expression (see internal model) + returnposition = returnposition -- number + } +end + +function M._inlinetyperef(definition) + return { + tag = "inlinetyperef", + def = definition, -- expression (see internal model) + + } +end + +function M._return(description) + return { + tag = "return", + description = description or "", -- string + types = {} -- list of typref (external or internal or primitive typeref) + } +end +return M diff --git a/Utils/luarocks/share/lua/5.1/models/apimodelbuilder.lua b/Utils/luarocks/share/lua/5.1/models/apimodelbuilder.lua new file mode 100644 index 000000000..6fcd3c8f5 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/models/apimodelbuilder.lua @@ -0,0 +1,459 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2011-2012 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: +-- Simon BERNARD +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local apimodel = require "models.apimodel" +local ldp = require "models.ldparser" +local Q = require "metalua.treequery" + +local M = {} + +local handledcomments={} -- cache to know the comment already handled + +---- +-- UTILITY METHODS +local primitivetypes = { + ['boolean'] = true, + ['function'] = true, + ['nil'] = true, + ['number'] = true, + ['string'] = true, + ['table'] = true, + ['thread'] = true, + ['userdata'] = true +} + +-- get or create the typedef with the name "name" +local function gettypedef(_file,name,kind,sourcerangemin,sourcerangemax) + local kind = kind or "recordtypedef" + local _typedef = _file.types[name] + if _typedef then + if _typedef.tag == kind then return _typedef end + else + if kind == "recordtypedef" and name ~= "global" then + local _recordtypedef = apimodel._recordtypedef(name) + + -- define sourcerange + _recordtypedef.sourcerange.min = sourcerangemin + _recordtypedef.sourcerange.max = sourcerangemax + + -- add to file if a name is defined + if _recordtypedef.name then _file:addtype(_recordtypedef) end + return _recordtypedef + elseif kind == "functiontypedef" then + -- TODO support function + return nil + else + return nil + end + end + return nil +end + + +-- create a typeref from the typref doc_tag +local function createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) + local _typeref + if dt_typeref.tag == "typeref" then + if dt_typeref.module then + -- manage external type + _typeref = apimodel._externaltypref() + _typeref.modulename = dt_typeref.module + _typeref.typename = dt_typeref.type + else + if primitivetypes[dt_typeref.type] then + -- manage primitive type + _typeref = apimodel._primitivetyperef() + _typeref.typename = dt_typeref.type + else + -- manage internal type + _typeref = apimodel._internaltyperef() + _typeref.typename = dt_typeref.type + if _file then + gettypedef(_file, _typeref.typename, "recordtypedef", sourcerangemin,sourcerangemax) + end + end + end + end + return _typeref +end + +-- create a return from the return doc_tag +local function createreturn(dt_return,_file,sourcerangemin,sourcerangemax) + local _return = apimodel._return() + + _return.description = dt_return.description + + -- manage typeref + if dt_return.types then + for _, dt_typeref in ipairs(dt_return.types) do + local _typeref = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) + if _typeref then + table.insert(_return.types,_typeref) + end + end + end + return _return +end + +-- create a item from the field doc_tag +local function createfield(dt_field,_file,sourcerangemin,sourcerangemax) + local _item = apimodel._item(dt_field.name) + + if dt_field.shortdescription then + _item.shortdescription = dt_field.shortdescription + _item.description = dt_field.description + else + _item.shortdescription = dt_field.description + end + + -- manage typeref + local dt_typeref = dt_field.type + if dt_typeref then + _item.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) + end + return _item +end + +-- create a param from the param doc_tag +local function createparam(dt_param,_file,sourcerangemin,sourcerangemax) + if not dt_param.name then return nil end + + local _parameter = apimodel._parameter(dt_param.name) + _parameter.description = dt_param.description + + -- manage typeref + local dt_typeref = dt_param.type + if dt_typeref then + _parameter.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) + end + return _parameter +end + +-- get or create the typedef with the name "name" +function M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) + if scope and not scope.module then + if _item.name then + if scope.type == "global" then + _file:addglobalvar(_item) + else + local _recordtypedef = gettypedef (_file, scope.type ,"recordtypedef",sourcerangemin,sourcerangemax) + _recordtypedef:addfield(_item) + end + else + -- if no item name precise we store the scope in the item to be able to add it to the right parent later + _item.scope = scope + end + end +end + +-- Function type counter +local i = 0 + +-- Reset function type counter +local function resetfunctiontypeidgenerator() + i = 0 +end + +-- Provides an unique index for a function type +local function generatefunctiontypeid() + i = i + 1 + return i +end + +-- generate a function type name +local function generatefunctiontypename(_functiontypedef) + local name = {"__"} + if _functiontypedef.returns and _functiontypedef.returns[1] then + local ret = _functiontypedef.returns[1] + for _, type in ipairs(ret.types) do + if type.typename then + if type.modulename then + table.insert(name,type.modulename) + end + table.insert(name,"#") + table.insert(name,type.typename) + end + end + + end + table.insert(name,"=") + if _functiontypedef.params then + for _, param in ipairs(_functiontypedef.params) do + local type = param.type + if type then + if type.typename then + if type.modulename then + table.insert(name,type.modulename) + end + table.insert(name,"#") + table.insert(name,type.typename) + else + table.insert(name,"#unknown") + end + end + table.insert(name,"[") + table.insert(name,param.name) + table.insert(name,"]") + end + end + table.insert(name,"__") + table.insert(name, generatefunctiontypeid()) + return table.concat(name) +end + + + +------------------------------------------------------ +-- create the module api +function M.createmoduleapi(ast,modulename) + + -- Initialise function type naming + resetfunctiontypeidgenerator() + + local _file = apimodel._file() + + local _comment2apiobj = {} + + local function handlecomment(comment) + + -- Extract information from tagged comments + local parsedcomment = ldp.parse(comment[1]) + if not parsedcomment then return nil end + + -- Get tags from the languages + local regulartags = parsedcomment.tags + + -- Will contain last API object generated from comments + local _lastapiobject + + -- if comment is an ld comment + if regulartags then + -- manage "module" comment + if regulartags["module"] then + -- get name + _file.name = regulartags["module"][1].name or modulename + _lastapiobject = _file + + -- manage descriptions + _file.shortdescription = parsedcomment.shortdescription + _file.description = parsedcomment.description + + local sourcerangemin = comment.lineinfo.first.offset + local sourcerangemax = comment.lineinfo.last.offset + + -- manage returns + if regulartags ["return"] then + for _, dt_return in ipairs(regulartags ["return"]) do + local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax) + table.insert(_file.returns,_return) + end + end + -- if no returns on module create a defaultreturn of type #modulename + if #_file.returns == 0 and _file.name then + -- create internal type ref + local _typeref = apimodel._internaltyperef() + _typeref.typename = _file.name + + -- create return + local _return = apimodel._return() + table.insert(_return.types,_typeref) + + -- add return + table.insert(_file.returns,_return) + + --create recordtypedef is not define + gettypedef(_file,_typeref.typename,"recordtypedef",sourcerangemin,sourcerangemax) + end + -- manage "type" comment + elseif regulartags["type"] and regulartags["type"][1].name ~= "global" then + local dt_type = regulartags["type"][1]; + -- create record type if it doesn't exist + local sourcerangemin = comment.lineinfo.first.offset + local sourcerangemax = comment.lineinfo.last.offset + local _recordtypedef = gettypedef (_file, dt_type.name ,"recordtypedef",sourcerangemin,sourcerangemax) + _lastapiobject = _recordtypedef + + -- re-set sourcerange in case the type was created before the type tag + _recordtypedef.sourcerange.min = sourcerangemin + _recordtypedef.sourcerange.max = sourcerangemax + + -- manage description + _recordtypedef.shortdescription = parsedcomment.shortdescription + _recordtypedef.description = parsedcomment.description + + -- manage fields + if regulartags["field"] then + for _, dt_field in ipairs(regulartags["field"]) do + local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax) + -- define sourcerange only if we create it + _item.sourcerange.min = sourcerangemin + _item.sourcerange.max = sourcerangemax + if _item then _recordtypedef:addfield(_item) end + end + end + elseif regulartags["field"] then + local dt_field = regulartags["field"][1] + + -- create item + local sourcerangemin = comment.lineinfo.first.offset + local sourcerangemax = comment.lineinfo.last.offset + local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax) + _item.shortdescription = parsedcomment.shortdescription + _item.description = parsedcomment.description + _lastapiobject = _item + + -- define sourcerange + _item.sourcerange.min = sourcerangemin + _item.sourcerange.max = sourcerangemax + + -- add item to its parent + local scope = regulartags["field"][1].parent + M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) + elseif regulartags["function"] or regulartags["param"] or regulartags["return"] then + -- create item + local _item = apimodel._item() + _item.shortdescription = parsedcomment.shortdescription + _item.description = parsedcomment.description + _lastapiobject = _item + + -- set name + if regulartags["function"] then _item.name = regulartags["function"][1].name end + + -- define sourcerange + local sourcerangemin = comment.lineinfo.first.offset + local sourcerangemax = comment.lineinfo.last.offset + _item.sourcerange.min = sourcerangemin + _item.sourcerange.max = sourcerangemax + + + -- create function type + local _functiontypedef = apimodel._functiontypedef() + _functiontypedef.shortdescription = parsedcomment.shortdescription + _functiontypedef.description = parsedcomment.description + + + -- manage params + if regulartags["param"] then + for _, dt_param in ipairs(regulartags["param"]) do + local _param = createparam(dt_param,_file,sourcerangemin,sourcerangemax) + table.insert(_functiontypedef.params,_param) + end + end + + -- manage returns + if regulartags["return"] then + for _, dt_return in ipairs(regulartags["return"]) do + local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax) + table.insert(_functiontypedef.returns,_return) + end + end + + -- add type name + _functiontypedef.name = generatefunctiontypename(_functiontypedef) + _file:addtype(_functiontypedef) + + -- create ref to this type + local _internaltyperef = apimodel._internaltyperef() + _internaltyperef.typename = _functiontypedef.name + _item.type=_internaltyperef + + -- add item to its parent + local sourcerangemin = comment.lineinfo.first.offset + local sourcerangemax = comment.lineinfo.last.offset + local scope = (regulartags["function"] and regulartags["function"][1].parent) or nil + M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) + end + end + + -- when we could not know which type of api object it is, we suppose this is an item + if not _lastapiobject then + _lastapiobject = apimodel._item() + _lastapiobject.shortdescription = parsedcomment.shortdescription + _lastapiobject.description = parsedcomment.description + _lastapiobject.sourcerange.min = comment.lineinfo.first.offset + _lastapiobject.sourcerange.max = comment.lineinfo.last.offset + end + + -- + -- Store user defined tags + -- + local thirdtags = parsedcomment and parsedcomment.unknowntags + if thirdtags then + -- Define a storage index for user defined tags on current API element + if not _lastapiobject.metadata then _lastapiobject.metadata = {} end + + -- Loop over user defined tags + for usertag, taglist in pairs(thirdtags) do + if not _lastapiobject.metadata[ usertag ] then + _lastapiobject.metadata[ usertag ] = { + tag = usertag + } + end + for _, tag in ipairs( taglist ) do + table.insert(_lastapiobject.metadata[usertag], tag) + end + end + end + + -- if we create an api object linked it to + _comment2apiobj[comment] =_lastapiobject + end + + local function parsecomment(node, parent, ...) + -- check for comments before this node + if node.lineinfo and node.lineinfo.first.comments then + local comments = node.lineinfo.first.comments + -- check all comments + for _,comment in ipairs(comments) do + -- if not already handled + if not handledcomments[comment] then + handlecomment(comment) + handledcomments[comment]=true + end + end + end + -- check for comments after this node + if node.lineinfo and node.lineinfo.last.comments then + local comments = node.lineinfo.last.comments + -- check all comments + for _,comment in ipairs(comments) do + -- if not already handled + if not handledcomments[comment] then + handlecomment(comment) + handledcomments[comment]=true + end + end + end + end + Q(ast):filter(function(x) return x.tag~=nil end):foreach(parsecomment) + return _file, _comment2apiobj +end + + +function M.extractlocaltype ( commentblock,_file) + if not commentblock then return nil end + + local stringcomment = commentblock[1] + + local parsedtag = ldp.parseinlinecomment(stringcomment) + if parsedtag then + local sourcerangemin = commentblock.lineinfo.first.offset + local sourcerangemax = commentblock.lineinfo.last.offset + + return createtyperef(parsedtag,_file,sourcerangemin,sourcerangemax), parsedtag.description + end + + return nil, stringcomment +end + +M.generatefunctiontypename = generatefunctiontypename + +return M \ No newline at end of file diff --git a/Utils/luarocks/share/lua/5.1/models/internalmodel.lua b/Utils/luarocks/share/lua/5.1/models/internalmodel.lua new file mode 100644 index 000000000..552521052 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/models/internalmodel.lua @@ -0,0 +1,65 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2012 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local M = {} + +function M._internalcontent() + return { + content = nil, -- block + unknownglobalvars = {}, -- list of item + tag = "MInternalContent" + } +end + +function M._block() + return { + content = {}, -- list of expr (identifier, index, call, invoke, block) + localvars = {}, -- list of {var=item, scope ={min,max}} + sourcerange = {min=0,max=0}, + tag = "MBlock" + } +end + +function M._identifier() + return { + definition = nil, -- item + sourcerange = {min=0,max=0}, + tag = "MIdentifier" + } +end + +function M._index(key, value) + return { + left= key, -- expr (identifier, index, call, invoke, block) + right= value, -- string + sourcerange = {min=0,max=0}, + tag = "MIndex" + } +end + +function M._call(funct) + return { + func = funct, -- expr (identifier, index, call, invoke, block) + sourcerange = {min=0,max=0}, + tag = "MCall" + } +end + +function M._invoke(name, expr) + return { + functionname = name, -- string + record = expr, -- expr (identifier, index, call, invoke, block) + sourcerange = {min=0,max=0}, + tag = "MInvoke" + } +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/models/internalmodelbuilder.mlua b/Utils/luarocks/share/lua/5.1/models/internalmodelbuilder.mlua new file mode 100644 index 000000000..4aeafa6cb --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/models/internalmodelbuilder.mlua @@ -0,0 +1,861 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2011-2012 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: +-- Simon BERNARD +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +-{ extension ('match', ...) } + +local Q = require 'metalua.treequery' + +local internalmodel = require 'models.internalmodel' +local apimodel = require 'models.apimodel' +local apimodelbuilder = require 'models.apimodelbuilder' + +local M = {} + +-- Analyzes an AST and returns two tables +-- * `locals`, which associates `Id{ } nodes which create a local variable +-- to a list of the `Id{ } occurrence nodes of that variable; +-- * `globals` which associates variable names to occurrences of +-- global variables having that name. +function bindings(ast) + local locals, globals = { }, { } + local function f(id, ...) + local name = id[1] + if Q.is_binder(id, ...) then + local binder = ... -- parent is the binder + locals[binder] = locals[binder] or { } + locals[binder][name]={ } + else + local _, binder = Q.get_binder(id, ...) + if binder then -- this is a local + table.insert(locals[binder][name], id) + else + local g = globals[name] + if g then table.insert(g, id) else globals[name]={id} end + end + end + end + Q(ast) :filter('Id') :foreach(f) + return locals, globals +end + +-- -------------------------------------- + +-- ---------------------------------------------------------- +-- return the comment linked before to this node +-- ---------------------------------------------------------- +local function getlinkedcommentbefore(node) + local function _getlinkedcomment(node,line) + if node and node.lineinfo and node.lineinfo.first.line == line then + -- get the last comment before (the nearest of code) + local comments = node.lineinfo.first.comments + local comment = comments and comments[#comments] + if comment and comment.lineinfo.last.line == line-1 then + -- ignore the comment if there are code before on the same line + if node.lineinfo.first.facing and (node.lineinfo.first.facing.line ~= comment.lineinfo.first.line) then + return comment + end + else + return _getlinkedcomment(node.parent,line) + end + end + return nil + end + + if node.lineinfo and node.lineinfo.first.line then + return _getlinkedcomment(node,node.lineinfo.first.line) + else + return nil + end +end + +-- ---------------------------------------------------------- +-- return the comment linked after to this node +-- ---------------------------------------------------------- +local function getlinkedcommentafter(node) + local function _getlinkedcomment(node,line) + if node and node.lineinfo and node.lineinfo.last.line == line then + -- get the first comment after (the nearest of code) + local comments = node.lineinfo.last.comments + local comment = comments and comments[1] + if comment and comment.lineinfo.first.line == line then + return comment + else + return _getlinkedcomment(node.parent,line) + end + end + return nil + end + + if node.lineinfo and node.lineinfo.last.line then + return _getlinkedcomment(node,node.lineinfo.last.line) + else + return nil + end +end + +-- ---------------------------------------------------------- +-- return true if this node is a block for the internal representation +-- ---------------------------------------------------------- +local supported_b = { + Function = true, + Do = true, + While = true, + Fornum = true, + Forin = true, + Repeat = true, +} +local function supportedblock(node, parent) + return supported_b[ node.tag ] or + (parent and parent.tag == "If" and node.tag == nil) +end + +-- ---------------------------------------------------------- +-- create a block from the metalua node +-- ---------------------------------------------------------- +local function createblock(block, parent) + local _block = internalmodel._block() + match block with + | `Function{param, body} + | `Do{...} + | `Fornum {identifier, min, max, body} + | `Forin {identifiers, exprs, body} + | `Repeat {body, expr} -> + _block.sourcerange.min = block.lineinfo.first.offset + _block.sourcerange.max = block.lineinfo.last.offset + | `While {expr, body} -> + _block.sourcerange.min = body.lineinfo.first.facing.offset + _block.sourcerange.max = body.lineinfo.last.facing.offset + | _ -> + if parent and parent.tag == "If" and block.tag == nil then + _block.sourcerange.min = block.lineinfo.first.facing.offset + _block.sourcerange.max = block.lineinfo.last.facing.offset + end + end + return _block +end + +-- ---------------------------------------------------------- +-- return true if this node is a expression in the internal representation +-- ---------------------------------------------------------- +local supported_e = { + Index = true, + Id = true, + Call = true, + Invoke = true +} +local function supportedexpr(node) + return supported_e[ node.tag ] +end + +local idto_block = {} -- cache from metalua id to internal model block +local idto_identifier = {} -- cache from metalua id to internal model indentifier +local expreto_expression = {} -- cache from metalua expression to internal model expression + +-- ---------------------------------------------------------- +-- create an expression from a metalua node +-- ---------------------------------------------------------- +local function createexpr(expr,_block) + local _expr = nil + + match expr with + | `Id { name } -> + -- we store the block which hold this node + -- to be able to define + idto_block[expr]= _block + + -- if expr has not line info, it means expr has no representation in the code + -- so we don't need it. + if not expr.lineinfo then return nil end + + -- create identifier + local _identifier = internalmodel._identifier() + idto_identifier[expr]= _identifier + _expr = _identifier + | `Index { innerexpr, `String{fieldname} } -> + if not expr.lineinfo then return nil end + -- create index + local _expression = createexpr(innerexpr,_block) + if _expression then _expr = internalmodel._index(_expression,fieldname) end + | `Call{innerexpr, ...} -> + if not expr.lineinfo then return nil end + -- create call + local _expression = createexpr(innerexpr,_block) + if _expression then _expr = internalmodel._call(_expression) end + | `Invoke{innerexpr,`String{functionname},...} -> + if not expr.lineinfo then return nil end + -- create invoke + local _expression = createexpr(innerexpr,_block) + if _expression then _expr = internalmodel._invoke(functionname,_expression) end + | _ -> + end + + if _expr then + _expr.sourcerange.min = expr.lineinfo.first.offset + _expr.sourcerange.max = expr.lineinfo.last.offset + + expreto_expression[expr] = _expr + end + + return _expr +end + +-- ---------------------------------------------------------- +-- create block and expression node +-- ---------------------------------------------------------- +local function createtreestructure(ast) + -- create internal content + local _internalcontent = internalmodel._internalcontent() + + -- create root block + local _block = internalmodel._block() + local _blocks = { _block } + _block.sourcerange.min = ast.lineinfo.first.facing.offset + -- TODO remove the math.max when we support partial AST + _block.sourcerange.max = math.max(ast.lineinfo.last.facing.offset, 10000) + + _internalcontent.content = _block + + -- visitor function (down) + local function down (node,parent) + if supportedblock(node,parent) then + -- create the block + local _block = createblock(node,parent) + -- add it to parent block + table.insert(_blocks[#_blocks].content, _block) + -- enqueue the last block to know the "current" block + table.insert(_blocks,_block) + elseif supportedexpr(node) then + -- we handle expression only if it was not already do + if not expreto_expression[node] then + -- create expr + local _expression = createexpr(node,_blocks[#_blocks]) + -- add it to parent block + if _expression then + table.insert(_blocks[#_blocks].content, _expression) + end + end + end + end + + -- visitor function (up) + local function up (node, parent) + if supportedblock(node,parent) then + -- dequeue the last block to know the "current" block + table.remove(_blocks,#_blocks) + end + end + + -- visit ast and build internal model + Q(ast):foreach(down,up) + + return _internalcontent +end + +local getitem + +-- ---------------------------------------------------------- +-- create the type from the node and position +-- ---------------------------------------------------------- +local function createtype(node,position,comment2apiobj,file) + -- create module type ref + match node with + | `Call{ `Id "require", `String {modulename}} -> + return apimodel._moduletyperef(modulename,position) + | `Function {params, body} -> + -- create the functiontypedef from code + local _functiontypedef = apimodel._functiontypedef() + for _, p in ipairs(params) do + -- create parameters + local paramname + if p.tag=="Dots" then + paramname = "..." + else + paramname = p[1] + end + local _param = apimodel._parameter(paramname) + table.insert(_functiontypedef.params,_param) + end + _functiontypedef.name = "___" -- no name for inline type + + return apimodel._inlinetyperef(_functiontypedef) + | `String {value} -> + local typeref = apimodel._primitivetyperef("string") + return typeref + | `Number {value} -> + local typeref = apimodel._primitivetyperef("number") + return typeref + | `True | `False -> + local typeref = apimodel._primitivetyperef("boolean") + return typeref + | `Table {...} -> + -- create recordtypedef from code + local _recordtypedef = apimodel._recordtypedef("___") -- no name for inline type + -- for each element of the table + for i=1,select("#", ...) do + local pair = select(i, ...) + -- if this is a pair we create a new item in the type + if pair.tag == "Pair" then + -- create an item + local _item = getitem(pair,nil, comment2apiobj,file) + if _item then + _recordtypedef:addfield(_item) + end + end + end + return apimodel._inlinetyperef(_recordtypedef) + | _ -> + end + -- if node is an expression supported + local supportedexpr = expreto_expression[node] + if supportedexpr then + -- create expression type ref + return apimodel._exprtyperef(supportedexpr,position) + end + +end + +local function completeapidoctype(apidoctype,itemname,init,file,comment2apiobj) + if not apidoctype.name then + apidoctype.name = itemname + file:mergetype(apidoctype) + end + + -- create type from code + local typeref = createtype(init,1,comment2apiobj,file) + if typeref and typeref.tag == "inlinetyperef" + and typeref.def.tag == "recordtypedef" then + + -- set the name + typeref.def.name = apidoctype.name + + -- merge the type with priority to documentation except for source range + file:mergetype(typeref.def,false,true) + end +end + +local function completeapidocitem (apidocitem, itemname, init, file, binder, comment2apiobj) + -- manage the case item has no name + if not apidocitem.name then + apidocitem.name = itemname + + -- if item has no name this means it could not be attach to a parent + if apidocitem.scope then + apimodelbuilder.additemtoparent(file,apidocitem,apidocitem.scope,apidocitem.sourcerange.min,apidocitem.sourcerange.max) + apidocitem.scope = nil + end + end + + -- for function try to merge definition + local apitype = apidocitem:resolvetype(file) + if apitype and apitype.tag == "functiontypedef" then + local codetype = createtype(init,1,comment2apiobj,file) + if codetype and codetype.tag =="inlinetyperef" then + codetype.def.name = apitype.name + file:mergetype(codetype.def) + end + end + + -- manage the case item has no type + if not apidocitem.type then + -- extract typing from comment + local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) + + if type then + apidocitem.type = type + else + -- if not found extracttype from code + apidocitem.type = createtype(init,1,comment2apiobj,file) + end + end +end + +-- ---------------------------------------------------------- +-- create or get the item finding in the binder with the given itemname +-- return also the ast node corresponding to this item +-- ---------------------------------------------------------- +getitem = function (binder, itemname, comment2apiobj, file) + + -- local function to create item + local function createitem(itemname, astnode, itemtype, description) + local _item = apimodel._item(itemname) + if description then _item.description = description end + _item.type = itemtype + if astnode and astnode.lineinfo then + _item.sourcerange.min = astnode.lineinfo.first.offset + _item.sourcerange.max = astnode.lineinfo.last.offset + end + return _item, astnode + end + + -- try to match binder with known patter of item declaration + match binder with + | `Pair {string, init} + | `Set { {`Index { right , string}}, {init,...}} if string and string.tag =="String" -> + -- Pair and set is for searching field from type .. + -- if the itemname is given this mean we search for a local or a global not a field type. + if not itemname then + local itemname = string[1] + + -- check for luadoc typing + local commentbefore = getlinkedcommentbefore(binder) + local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment + if apiobj then + if apiobj.tag=="item" then + if not apiobj.name or apiobj.name == itemname then + -- use code to complete api information if it's necessary + completeapidocitem(apiobj, itemname, init,file,binder,comment2apiobj) + -- for item use code source range rather than doc source range + if string and string.lineinfo then + apiobj.sourcerange.min = string.lineinfo.first.offset + apiobj.sourcerange.max = string.lineinfo.last.offset + end + return apiobj, string + end + elseif apiobj.tag=="recordtypedef" then + -- use code to complete api information if it's necessary + completeapidoctype(apiobj, itemname, init,file,comment2apiobj) + return createitem(itemname, string, apimodel._internaltyperef(apiobj.name), nil) + end + + -- if the apiobj could not be associated to the current obj, + -- we do not use the documentation neither + commentbefore = nil + end + + -- else we use code to extract the type and description + -- check for "local" typing + local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) + local desc = desc or (commentbefore and commentbefore[1]) + if type then + return createitem(itemname, string, type, desc ) + else + -- if no "local typing" extract type from code + return createitem(itemname, string, createtype(init,1,comment2apiobj,file), desc) + end + end + | `Set {ids, inits} + | `Local {ids, inits} -> + -- if this is a single local var declaration + -- we check if there are a comment block linked and try to extract the type + if #ids == 1 then + local currentid, currentinit = ids[1],inits[1] + -- ignore non Ids node + if currentid.tag ~= 'Id' or currentid[1] ~= itemname then return nil end + + -- check for luadoc typing + local commentbefore = getlinkedcommentbefore(binder) + local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment + if apiobj then + if apiobj.tag=="item" then + -- use code to complete api information if it's necessary + if not apiobj.name or apiobj.name == itemname then + completeapidocitem(apiobj, itemname, currentinit,file,binder,comment2apiobj) + -- if this is a global var or if is has no parent + -- we do not create a new item + if not apiobj.parent or apiobj.parent == file then + -- for item use code source range rather than doc source range + if currentid and currentid.lineinfo then + apiobj.sourcerange.min = currentid.lineinfo.first.offset + apiobj.sourcerange.max = currentid.lineinfo.last.offset + end + return apiobj, currentid + else + return createitem(itemname, currentid, apiobj.type, nil) + end + end + elseif apiobj.tag=="recordtypedef" then + -- use code to complete api information if it's necessary + completeapidoctype(apiobj, itemname, currentinit,file,comment2apiobj) + return createitem(itemname, currentid, apimodel._internaltyperef(apiobj.name), nil) + end + + -- if the apiobj could not be associated to the current obj, + -- we do not use the documentation neither + commentbefore = nil + end + + -- else we use code to extract the type and description + -- check for "local" typing + local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) + desc = desc or (commentbefore and commentbefore[1]) + if type then + return createitem(itemname, currentid, type, desc) + else + -- if no "local typing" extract type from code + return createitem(itemname, currentid, createtype(currentinit,1,comment2apiobj,file), desc) + end + end + -- else we use code to extract the type + local init,returnposition = nil,1 + for i,id in ipairs(ids) do + -- calculate the current return position + if init and (init.tag == "Call" or init.tag == "Invoke") then + -- if previous init was a call or an invoke + -- we increment the returnposition + returnposition= returnposition+1 + else + -- if init is not a function call + -- we change the init used to determine the type + init = inits[i] + end + + -- get the name of the current id + local idname = id[1] + + -- if this is the good id + if itemname == idname then + -- create type from init node and return position + return createitem (itemname, id, createtype(init,returnposition,comment2apiobj,file),nil) + end + end + | `Function {params, body} -> + for i,id in ipairs(params) do + -- get the name of the current id + local idname = id[1] + -- if this is the good id + if itemname == idname then + -- extract param's type from luadocumentation + local obj = comment2apiobj[getlinkedcommentbefore(binder)] + if obj and obj.tag=="item" then + local typedef = obj:resolvetype(file) + if typedef and typedef.tag =="functiontypedef" then + for j, param in ipairs(typedef.params) do + if i==j then + if i ==1 and itemname == "self" and param.type == nil + and obj.parent and obj.parent.tag == "recordtypedef" and obj.parent.name then + param.type = apimodel._internaltyperef(obj.parent.name) + end + -- TODO perhaps we must clone the typeref + return createitem(itemname,id, param.type,param.description) + end + end + end + end + return createitem(itemname,id) + end + end + | `Forin {ids, expr, body} -> + for i,id in ipairs(ids) do + -- get the name of the current id + local idname = id[1] + -- if this is the good id + if itemname == idname then + -- return data : we can not guess the type for now + return createitem(itemname,id) + end + end + | `Fornum {id, ...} -> + -- get the name of the current id + local idname = id[1] + -- if this is the good id + if itemname == idname then + -- return data : we can not guess the type for now + return createitem(itemname,id) + end + | `Localrec {{id}, {func}} -> + -- get the name of the current id + local idname = id[1] + -- if this is the good id + if itemname == idname then + -- check for luadoc typing + local commentbefore = getlinkedcommentbefore(binder) + local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment + if apiobj then + if apiobj.tag=="item" then + if not apiobj.name or apiobj.name == itemname then + -- use code to complete api information if it's necessary + completeapidocitem(apiobj, itemname, func,file,binder,comment2apiobj) + return createitem(itemname,id,apiobj.type,nil) + end + end + + -- if the apiobj could not be associated to the current obj, + -- we do not use the documentation neither + commentbefore = nil + end + + -- else we use code to extract the type and description + -- check for "local" typing + local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) + desc = desc or (commentbefore and commentbefore[1]) + if type then + return createitem(itemname, id, type, desc) + else + -- if no "local typing" extract type from code + return createitem(itemname, id, createtype(func,1,comment2apiobj,file), desc) + end + end + | _ -> + end +end + +-- ---------------------------------------------------------- +-- Search from Id node to Set node to find field of type. +-- +-- Lua code : table.field1.field2 = 12 +-- looks like that in metalua : +-- `Set{ +-- `Index { `Index { `Id "table", `String "field1" }, +-- `String "field2"}, +-- `Number "12"} +-- ---------------------------------------------------------- +local function searchtypefield(node,_currentitem,comment2apiobj,file) + + -- we are just interested : + -- by item which is field of recordtypedef + -- by ast node which are Index + if _currentitem then + local type = _currentitem:resolvetype(file) + if type and type.tag == "recordtypedef" then + if node and node.tag == "Index" then + local rightpart = node[2] + local _newcurrentitem = type.fields[rightpart[1]] + + if _newcurrentitem then + -- if this index represent a known field of the type we continue to search + searchtypefield (node.parent,_newcurrentitem,comment2apiobj,file) + else + -- if not, this is perhaps a new field, but + -- to be a new field this index must be include in a Set + if node.parent and node.parent.tag =="Set" then + -- in this case we create the new item ans add it to the type + local set = node.parent + local item, string = getitem(set,nil, comment2apiobj,file) + -- add this item to the type, only if it has no parent and if this type does not contain already this field + if item and not item.parent and string and not type.fields[string[1]] then + type:addfield(item) + end + end + end + end + end + end +end + +-- ---------------------------------------------------------- +-- create local vars, global vars and linked it with theirs occurences +-- ---------------------------------------------------------- +local function createvardefinitions(_internalcontent,ast,file,comment2apiobj) + -- use bindings to get locals and globals definition + local locals, globals = bindings( ast ) + + -- create locals var + for binder, namesAndOccurrences in pairs(locals) do + for name, occurrences in pairs(namesAndOccurrences) do + -- get item, id + local _item, id = getitem(binder, name,comment2apiobj,file) + if id then + -- add definition as occurence + -- we consider the identifier in the binder as an occurence + local _identifierdef = idto_identifier[id] + if _identifierdef then + table.insert(_item.occurrences, _identifierdef) + _identifierdef.definition = _item + end + + -- add occurences + for _,occurrence in ipairs(occurrences) do + searchtypefield(occurrence.parent, _item,comment2apiobj,file) + local _identifier = idto_identifier[occurrence] + if _identifier then + table.insert(_item.occurrences, _identifier) + _identifier.definition = _item + end + end + + -- add item to block + local _block = idto_block[id] + table.insert(_block.localvars,{item=_item,scope = {min=0,max=0}}) + end + end + end + + -- create globals var + for name, occurrences in pairs( globals ) do + + -- get or create definition + local _item = file.globalvars[name] + local binder = occurrences[1].parent + if not _item then + -- global declaration is only if the first occurence in left part of a 'Set' + if binder and binder.tag == "Set" then + _item = getitem(binder, name,comment2apiobj,file) + end + + -- if we find and item this is a global var declaration + if _item then + file:addglobalvar(_item) + else + -- else it is an unknown global var + _item = apimodel._item(name) + local _firstoccurrence = idto_identifier[occurrences[1]] + if _firstoccurrence then + _item.sourcerange.min = _firstoccurrence.sourcerange.min + _item.sourcerange.max = _firstoccurrence.sourcerange.max + end + table.insert(_internalcontent.unknownglobalvars,_item) + end + else + -- if the global var definition already exists, we just try to it + if binder then + match binder with + | `Set {ids, inits} -> + -- manage case only if there are 1 element in the Set + if #ids == 1 then + local currentid, currentinit = ids[1],inits[1] + -- ignore non Ids node and bad name + if currentid.tag == 'Id' and currentid[1] == name then + completeapidocitem(_item, name, currentinit,file,binder,comment2apiobj) + + if currentid and currentid.lineinfo then + _item.sourcerange.min = currentid.lineinfo.first.offset + _item.sourcerange.max = currentid.lineinfo.last.offset + end + end + end + | _ -> + end + end + end + + -- add occurences + for _,occurence in ipairs(occurrences) do + local _identifier = idto_identifier[occurence] + searchtypefield(occurence.parent, _item,comment2apiobj,file) + if _identifier then + table.insert(_item.occurrences, _identifier) + _identifier.definition = _item + end + end + end +end + +-- ---------------------------------------------------------- +-- add parent to all ast node +-- ---------------------------------------------------------- +local function addparents(ast) + -- visitor function (down) + local function down (node,parent) + node.parent = parent + end + + -- visit ast and build internal model + Q(ast):foreach(down,up) +end + +-- ---------------------------------------------------------- +-- try to detect a module declaration from code +-- ---------------------------------------------------------- +local function searchmodule(ast,file,comment2apiobj,modulename) + -- if the last statement is a return + if ast then + local laststatement = ast[#ast] + if laststatement and laststatement.tag == "Return" then + -- and if the first expression returned is an identifier. + local firstexpr = laststatement[1] + if firstexpr and firstexpr.tag == "Id" then + -- get identifier in internal model + local _identifier = idto_identifier [firstexpr] + -- the definition should be an inline type + if _identifier + and _identifier.definition + and _identifier.definition.type + and _identifier.definition.type.tag == "inlinetyperef" + and _identifier.definition.type.def.tag == "recordtypedef" then + + --set modulename if needed + if not file.name then file.name = modulename end + + -- create or merge type + local _type = _identifier.definition.type.def + _type.name = modulename + + -- if file (module) has no documentation add item documentation to it + -- else add it to the type. + if not file.description or file.description == "" then + file.description = _identifier.definition.description + else + _type.description = _identifier.definition.description + end + _identifier.definition.description = "" + if not file.shortdescription or file.shortdescription == "" then + file.shortdescription = _identifier.definition.shortdescription + else + _type.shortdescription = _identifier.definition.shortdescription + end + _identifier.definition.shortdescription = "" + + -- WORKAROUND FOR BUG 421622: [outline]module selection in outline does not select it in texteditor + --_type.sourcerange.min = _identifier.definition.sourcerange.min + --_type.sourcerange.max = _identifier.definition.sourcerange.max + + -- merge the type with priority to documentation except for source range + file:mergetype(_type,false,true) + + -- create return if needed + if not file.returns[1] then + file.returns[1] = apimodel._return() + file.returns[1].types = { apimodel._internaltyperef(modulename) } + end + + -- change the type of the identifier + _identifier.definition.type = apimodel._internaltyperef(modulename) + end + end + end + end +end + +-- ---------------------------------------------------------- +-- create the internalcontent from an ast metalua +-- ---------------------------------------------------------- +function M.createinternalcontent (ast,file,comment2apiobj,modulename) + -- init cache + idto_block = {} + idto_identifier = {} + expreto_expression = {} + comment2apiobj = comment2apiobj or {} + file = file or apimodel._file() + + -- execute code safely to be sure to clean cache correctly + local internalcontent + local ok, errmsg = pcall(function () + -- add parent to all node + addparents(ast) + + -- create block and expression node + internalcontent = createtreestructure(ast) + + -- create Local vars, global vars and linked occurences (Items) + createvardefinitions(internalcontent,ast,file,comment2apiobj) + + -- try to dectect module information from code + local moduletyperef = file:moduletyperef() + if moduletyperef and moduletyperef.tag == "internaltyperef" then + modulename = moduletyperef.typename or modulename + end + if modulename then + searchmodule(ast,file,comment2apiobj,modulename) + end + end) + + -- clean cache + idto_block = {} + idto_identifier = {} + expreto_expression = {} + + -- if not ok raise an error + if not ok then error (errmsg) end + + return internalcontent +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/models/ldparser.lua b/Utils/luarocks/share/lua/5.1/models/ldparser.lua new file mode 100644 index 000000000..d74071bf4 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/models/ldparser.lua @@ -0,0 +1,656 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2011-2013 Sierra Wireless and others. +-- 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: +-- Sierra Wireless - initial API and implementation +------------------------------------------------------------------------------- +local mlc = require ('metalua.compiler').new() +local gg = require 'metalua.grammar.generator' +local lexer = require 'metalua.grammar.lexer' +local mlp = mlc.parser + +local M = {} -- module +local lx -- lexer used to parse tag +local registeredparsers -- table {tagname => {list de parsers}} + +-- ---------------------------------------------------- +-- raise an error if result contains a node error +-- ---------------------------------------------------- +local function raiserror(result) + for i, node in ipairs(result) do + assert(not node or node.tag ~= "Error") + end +end + + +-- ---------------------------------------------------- +-- copy key and value from one table to an other +-- ---------------------------------------------------- +local function copykey(tablefrom, tableto) + for key, value in pairs(tablefrom) do + if key ~= "lineinfos" then + tableto[key] = value + end + end +end + +-- ---------------------------------------------------- +-- Handle keyword and identifiers as word +-- ---------------------------------------------------- +local function parseword(lx) + local word = lx :peek() + local tag = word.tag + + if tag=='Keyword' or tag=='Id' then + lx:next() + return {tag='Word', lineinfo=word.lineinfo, word[1]} + else + return gg.parse_error(lx,'Id or Keyword expected') + end +end + +-- ---------------------------------------------------- +-- parse an id +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local idparser = gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1][1] } + end, + parseword +}) + +-- ---------------------------------------------------- +-- parse a modulename (id.)?id +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local modulenameparser = gg.list({ + builder = function (result) + raiserror(result) + local ids = {} + for i, id in ipairs(result) do + table.insert(ids,id.name) + end + return {name = table.concat(ids,".")} + end, + primary = idparser, + separators = '.' +}) +-- ---------------------------------------------------- +-- parse a typename (id.)?id +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local typenameparser= modulenameparser + +-- ---------------------------------------------------- +-- parse an internaltype ref +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local internaltyperefparser = gg.sequence({ + builder = function(result) + raiserror(result) + return {tag = "typeref",type=result[1].name} + end, + "#", typenameparser +}) + +-- ---------------------------------------------------- +-- parse en external type ref +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local externaltyperefparser = gg.sequence({ + builder = function(result) + raiserror(result) + return {tag = "typeref",module=result[1].name,type=result[2].name} + end, + modulenameparser,"#", typenameparser +}) + + +-- ---------------------------------------------------- +-- parse a typeref +-- return a table {name, lineinfo) +-- ---------------------------------------------------- +local typerefparser = gg.multisequence{ + internaltyperefparser, + externaltyperefparser} + +-- ---------------------------------------------------- +-- parse a list of typeref +-- return a list of table {name, lineinfo) +-- ---------------------------------------------------- +local typereflistparser = gg.list({ + primary = typerefparser, + separators = ',' +}) + +-- ---------------------------------------------------- +-- TODO use a more generic way to parse (modifier if not always a typeref) +-- TODO support more than one modifier +-- ---------------------------------------------------- +local modifiersparser = gg.sequence({ + builder = function(result) + raiserror(result) + return {[result[1].name]=result[2]} + end, + "[", idparser , "=" , internaltyperefparser , "]" +}) + +-- ---------------------------------------------------- +-- parse a return tag +-- ---------------------------------------------------- +local returnparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + return { types= result[1]} + end, + '@','return', typereflistparser + }), + -- parser without typerefs + gg.sequence({ + builder = function (result) + raiserror(result) + return { types = {}} + end, + '@','return' + }) +} + +-- ---------------------------------------------------- +-- parse a param tag +-- ---------------------------------------------------- +local paramparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[2].name, type = result[1]} + end, + '@','param', typerefparser, idparser + }), + + -- full parser without type + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1].name} + end, + '@','param', idparser + }), + + -- Parser for `Dots + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = '...' } + end, + '@','param', '...' + }), +} +-- ---------------------------------------------------- +-- parse a field tag +-- ---------------------------------------------------- +local fieldparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + tag.type = result[2] + tag.name = result[3].name + return tag + end, + '@','field', modifiersparser, typerefparser, idparser + }), + + -- parser without name + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + tag.type = result[2] + return tag + end, + '@','field', modifiersparser, typerefparser + }), + + -- parser without type + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + tag.name = result[2].name + return tag + end, + '@','field', modifiersparser, idparser + }), + + -- parser without type and name + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + return tag + end, + '@','field', modifiersparser + }), + + -- parser without modifiers + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[2].name, type = result[1]} + end, + '@','field', typerefparser, idparser + }), + + -- parser without modifiers and name + gg.sequence({ + builder = function (result) + raiserror(result) + return {type = result[1]} + end, + '@','field', typerefparser + }), + + -- parser without type and modifiers + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1].name} + end, + '@','field', idparser + }), + + -- parser with nothing + gg.sequence({ + builder = function (result) + raiserror(result) + return {} + end, + '@','field' + }) +} + +-- ---------------------------------------------------- +-- parse a function tag +-- TODO use a more generic way to parse modifier ! +-- ---------------------------------------------------- +local functionparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + tag.name = result[2].name + return tag + end, + '@','function', modifiersparser, idparser + }), + + -- parser without name + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + copykey(result[1],tag) + return tag + end, + '@','function', modifiersparser + }), + + -- parser without modifier + gg.sequence({ + builder = function (result) + raiserror(result) + local tag = {} + tag.name = result[1].name + return tag + end, + '@','function', idparser + }), + + -- empty parser + gg.sequence({ + builder = function (result) + raiserror(result) + return {} + end, + '@','function' + }) +} + +-- ---------------------------------------------------- +-- parse a type tag +-- ---------------------------------------------------- +local typeparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1].name} + end, + '@','type',typenameparser + }), + -- parser without name + gg.sequence({ + builder = function (result) + raiserror(result) + return {} + end, + '@','type' + }) +} + +-- ---------------------------------------------------- +-- parse a module tag +-- ---------------------------------------------------- +local moduleparsers = { + -- full parser + gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1].name } + end, + '@','module', modulenameparser + }), + -- parser without name + gg.sequence({ + builder = function (result) + raiserror(result) + return {} + end, + '@','module' + }) +} + +-- ---------------------------------------------------- +-- parse a third tag +-- ---------------------------------------------------- +local thirdtagsparser = gg.sequence({ + builder = function (result) + raiserror(result) + return { name = result[1][1] } + end, + '@', mlp.id +}) +-- ---------------------------------------------------- +-- init parser +-- ---------------------------------------------------- +local function initparser() + -- register parsers + -- each tag name has several parsers + registeredparsers = { + ["module"] = moduleparsers, + ["return"] = returnparsers, + ["type"] = typeparsers, + ["field"] = fieldparsers, + ["function"] = functionparsers, + ["param"] = paramparsers + } + + -- create lexer used for parsing + lx = lexer.lexer:clone() + lx.extractors = { + -- "extract_long_comment", + -- "extract_short_comment", + -- "extract_long_string", + "extract_short_string", + "extract_word", + "extract_number", + "extract_symbol" + } + + -- Add dots as keyword + local tagnames = { '...' } + + -- Add tag names as key word + for tagname, _ in pairs(registeredparsers) do + table.insert(tagnames,tagname) + end + lx:add(tagnames) + + return lx, parsers +end + +initparser() + +-- ---------------------------------------------------- +-- get the string pattern to remove for each line of description +-- the goal is to fix the indentation problems +-- ---------------------------------------------------- +local function getstringtoremove (stringcomment,commentstart) + local _,_,capture = string.find(stringcomment,"\n?([ \t]*)@[^{]+",commentstart) + if not capture then + _,_,capture = string.find(stringcomment,"^([ \t]*)",commentstart) + end + capture = string.gsub(capture,"(.)","%1?") + return capture +end + +-- ---------------------------------------------------- +-- parse comment tag partition and return table structure +-- ---------------------------------------------------- +local function parsetag(part) + if part.comment:find("^@") then + -- check if the part start by a supported tag + for tagname,parsers in pairs(registeredparsers) do + if (part.comment:find("^@"..tagname)) then + -- try the registered parsers for this tag + local result + for i, parser in ipairs(parsers) do + local valid, tag = pcall(parser, lx:newstream(part.comment, tagname .. 'tag lexer')) + if valid then + -- add tagname + tag.tagname = tagname + + -- add description + local endoffset = tag.lineinfo.last.offset + tag.description = part.comment:sub(endoffset+2,-1) + return tag + end + end + end + end + end + return nil +end + +-- ---------------------------------------------------- +-- Parse third party tags. +-- +-- Enable to parse a tag not defined in language. +-- So for, accepted format is: @sometagname adescription +-- ---------------------------------------------------- +local function parsethirdtag( part ) + + -- Check it there is someting to process + if not part.comment:find("^@") then + return nil, 'No tag to parse' + end + + -- Apply parser + local status, parsedtag = pcall(thirdtagsparser, lx:newstream(part.comment, 'Third party tag lexer')) + if not status then + return nil, "Unable to parse given string." + end + + -- Retrieve description + local endoffset = parsedtag.lineinfo.last.offset + local tag = { + description = part.comment:sub(endoffset+2,-1) + } + return parsedtag.name, tag +end + +-- --------------------------------------------------------- +-- split string comment in several part +-- return list of {comment = string, offset = number} +-- the first part is the part before the first tag +-- the others are the part from a tag to the next one +-- ---------------------------------------------------- +local function split(stringcomment,commentstart) + local partstart = commentstart + local result = {} + + -- manage case where the comment start by @ + -- (we must ignore the inline see tag @{..}) + local at_startoffset, at_endoffset = stringcomment:find("^[ \t]*@[^{]",partstart) + if at_endoffset then + partstart = at_endoffset-1 -- we start before the @ and the non '{' character + end + + -- split comment + -- (we must ignore the inline see tag @{..}) + repeat + at_startoffset, at_endoffset = stringcomment:find("\n[ \t]*@[^{]",partstart) + local partend + if at_startoffset then + partend= at_startoffset-1 -- the end is before the separator pattern (just before the \n) + else + partend = #stringcomment -- we don't find any pattern so the end is the end of the string + end + table.insert(result, { comment = stringcomment:sub (partstart,partend) , + offset = partstart}) + if at_endoffset then + partstart = at_endoffset-1 -- the new start is befire the @ and the non { char + end + until not at_endoffset + return result +end + + +-- ---------------------------------------------------- +-- parse a comment block and return a table +-- ---------------------------------------------------- +function M.parse(stringcomment) + + local _comment = {description="", shortdescription=""} + + -- clean windows carriage return + stringcomment = string.gsub(stringcomment,"\r\n","\n") + + -- check if it's a ld comment + -- get the begin of the comment + -- ============================ + if not stringcomment:find("^-") then + -- if this comment don't start by -, we will not handle it. + return nil + end + + -- retrieve the real start + local commentstart = 2 --after the first hyphen + -- if the first line is an empty comment line with at least 3 hyphens we ignore it + local _ , endoffset = stringcomment:find("^-+[ \t]*\n") + if endoffset then + commentstart = endoffset+1 + end + + -- clean comments + -- =================== + -- remove line of "-" + stringcomment = string.sub(stringcomment,commentstart) + -- clean indentation + local pattern = getstringtoremove (stringcomment,1) + stringcomment = string.gsub(stringcomment,"^"..pattern,"") + stringcomment = string.gsub(stringcomment,"\n"..pattern,"\n") + + -- split comment part + -- ==================== + local commentparts = split(stringcomment, 1) + + -- Extract descriptions + -- ==================== + local firstpart = commentparts[1].comment + if firstpart:find("^[^@]") or firstpart:find("^@{") then + -- if the comment part don't start by @ + -- it's the part which contains descriptions + -- (there are an exception for the in-line see tag @{..}) + local shortdescription, description = string.match(firstpart,'^(.-[.?])(%s.+)') + -- store description + if shortdescription then + _comment.shortdescription = shortdescription + -- clean description + -- remove always the first space character + -- (this manage the case short and long description is on the same line) + description = string.gsub(description, "^[ \t]","") + -- if first line is only an empty string remove it + description = string.gsub(description, "^[ \t]*\n","") + _comment.description = description + else + _comment.shortdescription = firstpart + _comment.description = "" + end + end + + -- Extract tags + -- =================== + -- Parse regular tags + local tag + for i, part in ipairs(commentparts) do + tag = parsetag(part) + --if it's a supported tag (so tag is not nil, it's a table) + if tag then + if not _comment.tags then _comment.tags = {} end + if not _comment.tags[tag.tagname] then + _comment.tags[tag.tagname] = {} + end + table.insert(_comment.tags[tag.tagname], tag) + else + + -- Try user defined tags, so far they will look like + -- @identifier description + local tagname, thirdtag = parsethirdtag( part ) + if tagname then + -- + -- Append found tag + -- + local reservedname = 'unknowntags' + if not _comment.unknowntags then + _comment.unknowntags = {} + end + + -- Create specific section for parsed tag + if not _comment.unknowntags[tagname] then + _comment.unknowntags[tagname] = {} + end + -- Append to specific section + table.insert(_comment.unknowntags[tagname], thirdtag) + end + end + end + return _comment +end + + +function M.parseinlinecomment(stringcomment) + --TODO this code is use to activate typage only on --- comments. (deactivate for now) + -- if not stringcomment or not stringcomment:find("^-") then + -- -- if this comment don't start by -, we will not handle it. + -- return nil + -- end + -- -- remove the first '-' + -- stringcomment = string.sub(stringcomment,2) + -- print (stringcomment) + -- io.flush() + local valid, parsedtag = pcall(typerefparser, lx:newstream(stringcomment, 'typeref parser')) + if valid then + local endoffset = parsedtag.lineinfo.last.offset + parsedtag.description = stringcomment:sub(endoffset+2,-1) + return parsedtag + end +end + +return M diff --git a/Utils/luarocks/share/lua/5.1/pl/Date.lua b/Utils/luarocks/share/lua/5.1/pl/Date.lua new file mode 100644 index 000000000..c11a9ef18 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/Date.lua @@ -0,0 +1,546 @@ +--- Date and Date Format classes. +-- See the Guide. +-- @class module +-- @name pl.Date +-- @pragma nostrip + +--[[ +module("pl.Date") +]] + +local class = require 'pl.class' +local os_time, os_date = os.time, os.date +local stringx = require 'pl.stringx' + +local Date = class() +Date.Format = class() + +--- Date constructor. +-- @param t this can be either
      +--
    • nil - use current date and time
    • +--
    • number - seconds since epoch (as returned by @{os.time})
    • +--
    • Date - copy constructor
    • +--
    • table - table containing year, month, etc as for os.time() +-- You may leave out year, month or day, in which case current values will be used. +--
    • +--
    • two to six numbers: year, month, day, hour, min, sec +--
    +-- @function Date +function Date:_init(t,...) + local time + if select('#',...) > 0 then + local extra = {...} + local year = t + t = { + year = year, + month = extra[1], + day = extra[2], + hour = extra[3], + min = extra[4], + sec = extra[5] + } + end + if t == nil then + time = os_time() + elseif type(t) == 'number' then + time = t + elseif type(t) == 'table' then + if getmetatable(t) == Date then -- copy ctor + time = t.time + else + if not (t.year and t.month and t.year) then + local lt = os.date('*t') + if not t.year and not t.month and not t.day then + t.year = lt.year + t.month = lt.month + t.day = lt.day + else + t.year = t.year or lt.year + t.month = t.month or (t.day and lt.month or 1) + t.day = t.day or 1 + end + end + time = os_time(t) + end + end + self:set(time) +end + +local thour,tmin + +--- get the time zone offset from UTC. +-- @return hours ahead of UTC +-- @return minutes ahead of UTC +function Date.tzone () + if not thour then + local t = os.time() + local ut = os.date('!*t',t) + local lt = os.date('*t',t) + thour = lt.hour - ut.hour + tmin = lt.min - ut.min + end + return thour, tmin +end + +--- convert this date to UTC. +function Date:toUTC () + local th, tm = Date.tzone() + self:add { hour = -th } + + if tm > 0 then self:add {min = -tm} end +end + +--- convert this UTC date to local. +function Date:toLocal () + local th, tm = Date.tzone() + self:add { hour = th } + if tm > 0 then self:add {min = tm} end +end + +--- set the current time of this Date object. +-- @param t seconds since epoch +function Date:set(t) + self.time = t + self.tab = os_date('*t',self.time) +end + +--- set the year. +-- @param y Four-digit year +-- @class function +-- @name Date:year + +--- set the month. +-- @param m month +-- @class function +-- @name Date:month + +--- set the day. +-- @param d day +-- @class function +-- @name Date:day + +--- set the hour. +-- @param h hour +-- @class function +-- @name Date:hour + +--- set the minutes. +-- @param min minutes +-- @class function +-- @name Date:min + +--- set the seconds. +-- @param sec seconds +-- @class function +-- @name Date:sec + +--- set the day of year. +-- @class function +-- @param yday day of year +-- @name Date:yday + +--- get the year. +-- @param y Four-digit year +-- @class function +-- @name Date:year + +--- get the month. +-- @class function +-- @name Date:month + +--- get the day. +-- @class function +-- @name Date:day + +--- get the hour. +-- @class function +-- @name Date:hour + +--- get the minutes. +-- @class function +-- @name Date:min + +--- get the seconds. +-- @class function +-- @name Date:sec + +--- get the day of year. +-- @class function +-- @name Date:yday + + +for _,c in ipairs{'year','month','day','hour','min','sec','yday'} do + Date[c] = function(self,val) + if val then + self.tab[c] = val + self:set(os_time(self.tab)) + return self + else + return self.tab[c] + end + end +end + +--- name of day of week. +-- @param full abbreviated if true, full otherwise. +-- @return string name +function Date:weekday_name(full) + return os_date(full and '%A' or '%a',self.time) +end + +--- name of month. +-- @param full abbreviated if true, full otherwise. +-- @return string name +function Date:month_name(full) + return os_date(full and '%B' or '%b',self.time) +end + +--- is this day on a weekend?. +function Date:is_weekend() + return self.tab.wday == 0 or self.tab.wday == 6 +end + +--- add to a date object. +-- @param t a table containing one of the following keys and a value:
    +-- year,month,day,hour,min,sec +-- @return this date +function Date:add(t) + local key,val = next(t) + self.tab[key] = self.tab[key] + val + self:set(os_time(self.tab)) + return self +end + +--- last day of the month. +-- @return int day +function Date:last_day() + local d = 28 + local m = self.tab.month + while self.tab.month == m do + d = d + 1 + self:add{day=1} + end + self:add{day=-1} + return self +end + +--- difference between two Date objects. +-- Note: currently the result is a regular @{Date} object, +-- but also has `interval` field set, which means a more +-- appropriate string rep is used. +-- @param other Date object +-- @return a Date object +function Date:diff(other) + local dt = self.time - other.time + if dt < 0 then error("date difference is negative!",2) end + local date = Date(dt) + date.interval = true + return date +end + +--- long numerical ISO data format version of this date. +function Date:__tostring() + if not self.interval then + return os_date('%Y-%m-%d %H:%M:%S',self.time) + else + local t, res = self.tab, '' + local y,m,d = t.year - 1970, t.month - 1, t.day - 1 + if y > 0 then res = res .. y .. ' years ' end + if m > 0 then res = res .. m .. ' months ' end + if d > 0 then res = res .. d .. ' days ' end + if y == 0 and m == 0 then + local h = t.hour - Date.tzone() -- not accounting for UTC mins! + if h > 0 then res = res .. h .. ' hours ' end + if t.min > 0 then res = res .. t.min .. ' min ' end + if t.sec > 0 then res = res .. t.sec .. ' sec ' end + end + return res + end +end + +--- equality between Date objects. +function Date:__eq(other) + return self.time == other.time +end + +--- equality between Date objects. +function Date:__lt(other) + return self.time < other.time +end + + +------------ Date.Format class: parsing and renderinig dates ------------ + +-- short field names, explicit os.date names, and a mask for allowed field repeats +local formats = { + d = {'day',{true,true}}, + y = {'year',{false,true,false,true}}, + m = {'month',{true,true}}, + H = {'hour',{true,true}}, + M = {'min',{true,true}}, + S = {'sec',{true,true}}, +} + +-- + +--- Date.Format constructor. +-- @param fmt. A string where the following fields are significant:
      +--
    • d day (either d or dd)
    • +--
    • y year (either yy or yyy)
    • +--
    • m month (either m or mm)
    • +--
    • H hour (either H or HH)
    • +--
    • M minute (either M or MM)
    • +--
    • S second (either S or SS)
    • +--
    +-- Alternatively, if fmt is nil then this returns a flexible date parser +-- that tries various date/time schemes in turn: +--
      +--
    1. ISO 8601, +-- like 2010-05-10 12:35:23Z or 2008-10-03T14:30+02
    2. +--
    3. times like 15:30 or 8.05pm (assumed to be today's date)
    4. +--
    5. dates like 28/10/02 (European order!) or 5 Feb 2012
    6. +--
    7. month name like march or Mar (case-insensitive, first 3 letters); +-- here the day will be 1 and the year this current year
    8. +--
    +-- A date in format 3 can be optionally followed by a time in format 2. +-- Please see test-date.lua in the tests folder for more examples. +-- @usage df = Date.Format("yyyy-mm-dd HH:MM:SS") +-- @class function +-- @name Date.Format +function Date.Format:_init(fmt) + if not fmt then return end + local append = table.insert + local D,PLUS,OPENP,CLOSEP = '\001','\002','\003','\004' + local vars,used = {},{} + local patt,outf = {},{} + local i = 1 + while i < #fmt do + local ch = fmt:sub(i,i) + local df = formats[ch] + if df then + if used[ch] then error("field appeared twice: "..ch,2) end + used[ch] = true + -- this field may be repeated + local _,inext = fmt:find(ch..'+',i+1) + local cnt = not _ and 1 or inext-i+1 + if not df[2][cnt] then error("wrong number of fields: "..ch,2) end + -- single chars mean 'accept more than one digit' + local p = cnt==1 and (D..PLUS) or (D):rep(cnt) + append(patt,OPENP..p..CLOSEP) + append(vars,ch) + if ch == 'y' then + append(outf,cnt==2 and '%y' or '%Y') + else + append(outf,'%'..ch) + end + i = i + cnt + else + append(patt,ch) + append(outf,ch) + i = i + 1 + end + end + -- escape any magic characters + fmt = table.concat(patt):gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1') + -- replace markers with their magic equivalents + fmt = fmt:gsub(D,'%%d'):gsub(PLUS,'+'):gsub(OPENP,'('):gsub(CLOSEP,')') + self.fmt = fmt + self.outf = table.concat(outf) + self.vars = vars + +end + +local parse_date + +--- parse a string into a Date object. +-- @param str a date string +-- @return date object +function Date.Format:parse(str) + if not self.fmt then + return parse_date(str,self.us) + end + local res = {str:match(self.fmt)} + if #res==0 then return nil, 'cannot parse '..str end + local tab = {} + for i,v in ipairs(self.vars) do + local name = formats[v][1] -- e.g. 'y' becomes 'year' + tab[name] = tonumber(res[i]) + end + -- os.date() requires these fields; if not present, we assume + -- that the time set is for the current day. + if not (tab.year and tab.month and tab.year) then + local today = Date() + tab.year = tab.year or today:year() + tab.month = tab.month or today:month() + tab.day = tab.day or today:month() + end + local Y = tab.year + if Y < 100 then -- classic Y2K pivot + tab.year = Y + (Y < 35 and 2000 or 1999) + elseif not Y then + tab.year = 1970 + end + --dump(tab) + return Date(tab) +end + +--- convert a Date object into a string. +-- @param d a date object, or a time value as returned by @{os.time} +-- @return string +function Date.Format:tostring(d) + local tm = type(d) == 'number' and d or d.time + if self.outf then + return os.date(self.outf,tm) + else + return tostring(Date(d)) + end +end + +function Date.Format:US_order(yesno) + self.us = yesno +end + +local months = {jan=1,feb=2,mar=3,apr=4,may=5,jun=6,jul=7,aug=8,sep=9,oct=10,nov=11,dec=12} + +--[[ +Allowed patterns: +- [day] [monthname] [year] [time] +- [day]/[month][/year] [time] + +]] + + +local is_word = stringx.isalpha +local is_number = stringx.isdigit +local function tonum(s,l1,l2,kind) + kind = kind or '' + local n = tonumber(s) + if not n then error(("%snot a number: '%s'"):format(kind,s)) end + if n < l1 or n > l2 then + error(("%s out of range: %s is not between %d and %d"):format(kind,s,l1,l2)) + end + return n +end + +local function parse_iso_end(p,ns,sec) + -- may be fractional part of seconds + local _,nfrac,secfrac = p:find('^%.%d+',ns+1) + if secfrac then + sec = sec .. secfrac + p = p:sub(nfrac+1) + else + p = p:sub(ns+1) + end + -- ISO 8601 dates may end in Z (for UTC) or [+-][isotime] + if p:match 'z$' then return sec, {h=0,m=0} end -- we're UTC! + p = p:gsub(':','') -- turn 00:30 to 0030 + local _,_,sign,offs = p:find('^([%+%-])(%d+)') + if not sign then return sec, nil end -- not UTC + + if #offs == 2 then offs = offs .. '00' end -- 01 to 0100 + local tz = { h = tonumber(offs:sub(1,2)), m = tonumber(offs:sub(3,4)) } + if sign == '-' then tz.h = -tz.h; tz.m = -tz.m end + return sec, tz +end + +local function parse_date_unsafe (s,US) + s = s:gsub('T',' ') -- ISO 8601 + local parts = stringx.split(s:lower()) + local i,p = 1,parts[1] + local function nextp() i = i + 1; p = parts[i] end + local year,min,hour,sec,apm + local tz + local _,nxt,day, month = p:find '^(%d+)/(%d+)' + if day then + -- swop for US case + if US then + day, month = month, day + end + _,_,year = p:find('^/(%d+)',nxt+1) + nextp() + else -- ISO + year,month,day = p:match('^(%d+)%-(%d+)%-(%d+)') + if year then + nextp() + end + end + if p and not year and is_number(p) then -- has to be date + day = p + nextp() + end + if p and is_word(p) then + p = p:sub(1,3) + local mon = months[p] + if mon then + month = mon + else error("not a month: " .. p) end + nextp() + end + if p and not year and is_number(p) then + year = p + nextp() + end + + if p then -- time is hh:mm[:ss], hhmm[ss] or H.M[am|pm] + _,nxt,hour,min = p:find '^(%d+):(%d+)' + local ns + if nxt then -- are there seconds? + _,ns,sec = p:find ('^:(%d+)',nxt+1) + --if ns then + sec,tz = parse_iso_end(p,ns or nxt,sec) + --end + else -- might be h.m + _,ns,hour,min = p:find '^(%d+)%.(%d+)' + if ns then + apm = p:match '[ap]m$' + else -- or hhmm[ss] + local hourmin + _,nxt,hourmin = p:find ('^(%d+)') + if nxt then + hour = hourmin:sub(1,2) + min = hourmin:sub(3,4) + sec = hourmin:sub(5,6) + if #sec == 0 then sec = nil end + sec,tz = parse_iso_end(p,nxt,sec) + end + end + end + end + local today + if not (year and month and day) then + today = Date() + end + day = day and tonum(day,1,31,'day') or (month and 1 or today:day()) + month = month and tonum(month,1,12,'month') or today:month() + year = year and tonumber(year) or today:year() + if year < 100 then -- two-digit year pivot + year = year + (year < 35 and 2000 or 1900) + end + hour = hour and tonum(hour,1,apm and 12 or 24,'hour') or 12 + if apm == 'pm' then + hour = hour + 12 + end + min = min and tonum(min,1,60) or 0 + sec = sec and tonum(sec,1,60) or 0 + local res = Date {year = year, month = month, day = day, hour = hour, min = min, sec = sec} + if tz then -- ISO 8601 UTC time + res:toUTC() + res:add {hour = tz.h} + if tz.m ~= 0 then res:add {min = tz.m} end + end + return res +end + +function parse_date (s) + local ok, d = pcall(parse_date_unsafe,s) + if not ok then -- error + d = d:gsub('.-:%d+: ','') + return nil, d + else + return d + end +end + + +return Date + diff --git a/Utils/luarocks/share/lua/5.1/pl/List.lua b/Utils/luarocks/share/lua/5.1/pl/List.lua new file mode 100644 index 000000000..776f4d938 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/List.lua @@ -0,0 +1,553 @@ +--- Python-style list class.

    +-- Based on original code by Nick Trout. +--

    +-- Please Note: methods that change the list will return the list. +-- This is to allow for method chaining, but please note that ls = ls:sort() +-- does not mean that a new copy of the list is made. In-place (mutable) methods +-- are marked as returning 'the list' in this documentation. +--

    +-- See the Guide for further discussion +--

    +-- See http://www.python.org/doc/current/tut/tut.html, section 5.1 +--

    +-- Note: The comments before some of the functions are from the Python docs +-- and contain Python code. +--

    +-- Written for Lua version 4.0
    +-- Redone for Lua 5.1, Steve Donovan. +-- @class module +-- @name pl.List +-- @pragma nostrip + +local tinsert,tremove,concat,tsort = table.insert,table.remove,table.concat,table.sort +local setmetatable, getmetatable,type,tostring,assert,string,next = setmetatable,getmetatable,type,tostring,assert,string,next +local write = io.write +local tablex = require 'pl.tablex' +local filter,imap,imap2,reduce,transform,tremovevalues = tablex.filter,tablex.imap,tablex.imap2,tablex.reduce,tablex.transform,tablex.removevalues +local tablex = tablex +local tsub = tablex.sub +local utils = require 'pl.utils' +local function_arg = utils.function_arg +local is_type = utils.is_type +local split = utils.split +local assert_arg = utils.assert_arg +local normalize_slice = tablex._normalize_slice + +--[[ +module ('pl.List',utils._module) +]] + +local Multimap = utils.stdmt.MultiMap +-- metatable for our list objects +local List = utils.stdmt.List +List.__index = List +List._class = List + +local iter + +-- we give the metatable its own metatable so that we can call it like a function! +setmetatable(List,{ + __call = function (tbl,arg) + return List.new(arg) + end, +}) + +local function makelist (t,obj) + local klass = List + if obj then + klass = getmetatable(obj) + end + return setmetatable(t,klass) +end + +local function is_list(t) + return getmetatable(t) == List +end + +local function simple_table(t) + return type(t) == 'table' and not is_list(t) and #t > 0 +end + +function List:_init (src) + if src then + for v in iter(src) do + tinsert(self,v) + end + end +end + +--- Create a new list. Can optionally pass a table; +-- passing another instance of List will cause a copy to be created +-- we pass anything which isn't a simple table to iterate() to work out +-- an appropriate iterator @see List.iterate +-- @param t An optional list-like table +-- @return a new List +-- @usage ls = List(); ls = List {1,2,3,4} +function List.new(t) + local ls + if not simple_table(t) then + ls = {} + List._init(ls,t) + else + ls = t + end + makelist(ls) + return ls +end + +function List:clone() + local ls = makelist({},self) + List._init(ls,self) + return ls +end + +function List.default_map_with(T) + return function(self,name) + local f = T[name] + if f then + return function(self,...) + return self:map(f,...) + end + else + error("method not found: "..name,2) + end + end +end + + +---Add an item to the end of the list. +-- @param i An item +-- @return the list +function List:append(i) + tinsert(self,i) + return self +end + +List.push = tinsert + +--- Extend the list by appending all the items in the given list. +-- equivalent to 'a[len(a):] = L'. +-- @param L Another List +-- @return the list +function List:extend(L) + assert_arg(1,L,'table') + for i = 1,#L do tinsert(self,L[i]) end + return self +end + +--- Insert an item at a given position. i is the index of the +-- element before which to insert. +-- @param i index of element before whichh to insert +-- @param x A data item +-- @return the list +function List:insert(i, x) + assert_arg(1,i,'number') + tinsert(self,i,x) + return self +end + +--- Insert an item at the begining of the list. +-- @param x a data item +-- @return the list +function List:put (x) + return self:insert(1,x) +end + +--- Remove an element given its index. +-- (equivalent of Python's del s[i]) +-- @param i the index +-- @return the list +function List:remove (i) + assert_arg(1,i,'number') + tremove(self,i) + return self +end + +--- Remove the first item from the list whose value is given. +-- (This is called 'remove' in Python; renamed to avoid confusion +-- with table.remove) +-- Return nil if there is no such item. +-- @param x A data value +-- @return the list +function List:remove_value(x) + for i=1,#self do + if self[i]==x then tremove(self,i) return self end + end + return self + end + +--- Remove the item at the given position in the list, and return it. +-- If no index is specified, a:pop() returns the last item in the list. +-- The item is also removed from the list. +-- @param i An index +-- @return the item +function List:pop(i) + if not i then i = #self end + assert_arg(1,i,'number') + return tremove(self,i) +end + +List.get = List.pop + +--- Return the index in the list of the first item whose value is given. +-- Return nil if there is no such item. +-- @class function +-- @name List:index +-- @param x A data value +-- @param idx where to start search (default 1) +-- @return the index, or nil if not found. + +local tfind = tablex.find +List.index = tfind + +--- does this list contain the value?. +-- @param x A data value +-- @return true or false +function List:contains(x) + return tfind(self,x) and true or false +end + +--- Return the number of times value appears in the list. +-- @param x A data value +-- @return number of times x appears +function List:count(x) + local cnt=0 + for i=1,#self do + if self[i]==x then cnt=cnt+1 end + end + return cnt +end + +--- Sort the items of the list, in place. +-- @param cmp an optional comparison function; '<' is used if not given. +-- @return the list +function List:sort(cmp) + tsort(self,cmp) + return self +end + +--- Reverse the elements of the list, in place. +-- @return the list +function List:reverse() + local t = self + local n = #t + local n2 = n/2 + for i = 1,n2 do + local k = n-i+1 + t[i],t[k] = t[k],t[i] + end + return self +end + +--- Emulate list slicing. like 'list[first:last]' in Python. +-- If first or last are negative then they are relative to the end of the list +-- eg. slice(-2) gives last 2 entries in a list, and +-- slice(-4,-2) gives from -4th to -2nd +-- @param first An index +-- @param last An index +-- @return a new List +function List:slice(first,last) + return tsub(self,first,last) +end + +--- empty the list. +-- @return the list +function List:clear() + for i=1,#self do tremove(self,i) end + return self +end + +local eps = 1.0e-10 + +--- Emulate Python's range(x) function. +-- Include it in List table for tidiness +-- @param start A number +-- @param finish A number greater than start; if zero, then 0..start-1 +-- @param incr an optional increment (may be less than 1) +-- @usage List.range(0,3) == List {0,1,2,3} +function List.range(start,finish,incr) + if not finish then + start = 0 + finish = finish - 1 + end + if incr then + if not utils.is_integer(incr) then finish = finish + eps end + else + incr = 1 + end + assert_arg(1,start,'number') + assert_arg(2,finish,'number') + local t = List.new() + for i=start,finish,incr do tinsert(t,i) end + return t +end + +--- list:len() is the same as #list. +function List:len() + return #self +end + +-- Extended operations -- + +--- Remove a subrange of elements. +-- equivalent to 'del s[i1:i2]' in Python. +-- @param i1 start of range +-- @param i2 end of range +-- @return the list +function List:chop(i1,i2) + return tremovevalues(self,i1,i2) +end + +--- Insert a sublist into a list +-- equivalent to 's[idx:idx] = list' in Python +-- @param idx index +-- @param list list to insert +-- @return the list +-- @usage l = List{10,20}; l:splice(2,{21,22}); assert(l == List{10,21,22,20}) +function List:splice(idx,list) + assert_arg(1,idx,'number') + idx = idx - 1 + local i = 1 + for v in iter(list) do + tinsert(self,i+idx,v) + i = i + 1 + end + return self +end + +--- general slice assignment s[i1:i2] = seq. +-- @param i1 start index +-- @param i2 end index +-- @param seq a list +-- @return the list +function List:slice_assign(i1,i2,seq) + assert_arg(1,i1,'number') + assert_arg(1,i2,'number') + i1,i2 = normalize_slice(self,i1,i2) + if i2 >= i1 then self:chop(i1,i2) end + self:splice(i1,seq) + return self +end + +--- concatenation operator. +-- @param L another List +-- @return a new list consisting of the list with the elements of the new list appended +function List:__concat(L) + assert_arg(1,L,'table') + local ls = self:clone() + ls:extend(L) + return ls +end + +--- equality operator ==. True iff all elements of two lists are equal. +-- @param L another List +-- @return true or false +function List:__eq(L) + if #self ~= #L then return false end + for i = 1,#self do + if self[i] ~= L[i] then return false end + end + return true +end + +--- join the elements of a list using a delimiter.
    +-- This method uses tostring on all elements. +-- @param delim a delimiter string, can be empty. +-- @return a string +function List:join (delim) + delim = delim or '' + assert_arg(1,delim,'string') + return concat(imap(tostring,self),delim) +end + +--- join a list of strings.
    +-- Uses table.concat directly. +-- @class function +-- @name List:concat +-- @param delim a delimiter +-- @return a string +List.concat = concat + +local function tostring_q(val) + local s = tostring(val) + if type(val) == 'string' then + s = '"'..s..'"' + end + return s +end + +--- how our list should be rendered as a string. Uses join(). +-- @see List:join +function List:__tostring() + return '{'..self:join(',',tostring_q)..'}' +end + +--[[ +-- NOTE: this works, but is unreliable. If you leave the loop before finishing, +-- then the iterator is not reset. +--- can iterate over a list directly. +-- @usage for v in ls do print(v) end +function List:__call() + if not self.key then self.key = 1 end + local value = self[self.key] + self.key = self.key + 1 + if not value then self.key = nil end + return value +end +--]] + +--[[ +function List.__call(t,v,i) + i = (i or 0) + 1 + v = t[i] + if v then return i, v end +end +--]] + +--- call the function for each element of the list. +-- @param fun a function or callable object +-- @param ... optional values to pass to function +function List:foreach (fun,...) + local t = self + fun = function_arg(1,fun) + for i = 1,#t do + fun(t[i],...) + end +end + +--- create a list of all elements which match a function. +-- @param fun a boolean function +-- @param arg optional argument to be passed as second argument of the predicate +-- @return a new filtered list. +function List:filter (fun,arg) + return makelist(filter(self,fun,arg),self) +end + +--- split a string using a delimiter. +-- @param s the string +-- @param delim the delimiter (default spaces) +-- @return a List of strings +-- @see pl.utils.split +function List.split (s,delim) + assert_arg(1,s,'string') + return makelist(split(s,delim)) +end + +--- apply a function to all elements. +-- Any extra arguments will be passed to the function +-- @param fun a function of at least one argument +-- @param ... arbitrary extra arguments. +-- @return a new list: {f(x) for x in self} +-- @see pl.tablex.imap +function List:map (fun,...) + return makelist(imap(fun,self,...),self) +end + +--- apply a function to all elements, in-place. +-- Any extra arguments are passed to the function. +-- @param fun A function that takes at least one argument +-- @param ... arbitrary extra arguments. +function List:transform (fun,...) + transform(fun,self,...) +end + +--- apply a function to elements of two lists. +-- Any extra arguments will be passed to the function +-- @param fun a function of at least two arguments +-- @param ls another list +-- @param ... arbitrary extra arguments. +-- @return a new list: {f(x,y) for x in self, for x in arg1} +-- @see pl.tablex.imap2 +function List:map2 (fun,ls,...) + return makelist(imap2(fun,self,ls,...),self) +end + +--- apply a named method to all elements. +-- Any extra arguments will be passed to the method. +-- @param name name of method +-- @param ... extra arguments +-- @return a new list of the results +-- @see pl.seq.mapmethod +function List:mapm (name,...) + local res = {} + local t = self + for i = 1,#t do + local val = t[i] + local fn = val[name] + if not fn then error(type(val).." does not have method "..name,2) end + res[i] = fn(val,...) + end + return makelist(res,self) +end + +--- 'reduce' a list using a binary function. +-- @param fun a function of two arguments +-- @return result of the function +-- @see pl.tablex.reduce +function List:reduce (fun) + return reduce(fun,self) +end + +--- partition a list using a classifier function. +-- The function may return nil, but this will be converted to the string key ''. +-- @param fun a function of at least one argument +-- @param ... will also be passed to the function +-- @return a table where the keys are the returned values, and the values are Lists +-- of values where the function returned that key. It is given the type of Multimap. +-- @see pl.MultiMap +function List:partition (fun,...) + fun = function_arg(1,fun) + local res = {} + for i = 1,#self do + local val = self[i] + local klass = fun(val,...) + if klass == nil then klass = '' end + if not res[klass] then res[klass] = List() end + res[klass]:append(val) + end + return setmetatable(res,Multimap) +end + +--- return an iterator over all values. +function List:iter () + return iter(self) +end + +--- Create an iterator over a seqence. +-- This captures the Python concept of 'sequence'. +-- For tables, iterates over all values with integer indices. +-- @param seq a sequence; a string (over characters), a table, a file object (over lines) or an iterator function +-- @usage for x in iterate {1,10,22,55} do io.write(x,',') end ==> 1,10,22,55 +-- @usage for ch in iterate 'help' do do io.write(ch,' ') end ==> h e l p +function List.iterate(seq) + if type(seq) == 'string' then + local idx = 0 + local n = #seq + local sub = string.sub + return function () + idx = idx + 1 + if idx > n then return nil + else + return sub(seq,idx,idx) + end + end + elseif type(seq) == 'table' then + local idx = 0 + local n = #seq + return function() + idx = idx + 1 + if idx > n then return nil + else + return seq[idx] + end + end + elseif type(seq) == 'function' then + return seq + elseif type(seq) == 'userdata' and io.type(seq) == 'file' then + return seq:lines() + end +end +iter = List.iterate + +return List + diff --git a/Utils/luarocks/share/lua/5.1/pl/Map.lua b/Utils/luarocks/share/lua/5.1/pl/Map.lua new file mode 100644 index 000000000..8b7284037 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/Map.lua @@ -0,0 +1,108 @@ +--- A Map class. +-- @class module +-- @name pl.Map + +--[[ +module ('pl.Map') +]] +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local stdmt = utils.stdmt +local is_callable = utils.is_callable +local tmakeset,deepcompare,merge,keys,difference,tupdate = tablex.makeset,tablex.deepcompare,tablex.merge,tablex.keys,tablex.difference,tablex.update + +local pretty_write = require 'pl.pretty' . write +local Map = stdmt.Map +local Set = stdmt.Set +local List = stdmt.List + +local class = require 'pl.class' + +-- the Map class --------------------- +class(nil,nil,Map) + +local function makemap (m) + return setmetatable(m,Map) +end + +function Map:_init (t) + local mt = getmetatable(t) + if mt == Set or mt == Map then + self:update(t) + else + return t -- otherwise assumed to be a map-like table + end +end + + +local function makelist(t) + return setmetatable(t,List) +end + +--- list of keys. +Map.keys = tablex.keys + +--- list of values. +Map.values = tablex.values + +--- return an iterator over all key-value pairs. +function Map:iter () + return pairs(self) +end + +--- return a List of all key-value pairs, sorted by the keys. +function Map:items() + local ls = makelist(tablex.pairmap (function (k,v) return makelist {k,v} end, self)) + ls:sort(function(t1,t2) return t1[1] < t2[1] end) + return ls +end + +-- Will return the existing value, or if it doesn't exist it will set +-- a default value and return it. +function Map:setdefault(key, defaultval) + return self[key] or self:set(key,defaultval) or defaultval +end + +--- size of map. +-- note: this is a relatively expensive operation! +-- @class function +-- @name Map:len +Map.len = tablex.size + +--- put a value into the map. +-- @param key the key +-- @param val the value +function Map:set (key,val) + self[key] = val +end + +--- get a value from the map. +-- @param key the key +-- @return the value, or nil if not found. +function Map:get (key) + return rawget(self,key) +end + +local index_by = tablex.index_by + +-- get a list of values indexed by a list of keys. +-- @param keys a list-like table of keys +-- @return a new list +function Map:getvalues (keys) + return makelist(index_by(self,keys)) +end + +Map.iter = pairs + +Map.update = tablex.update + +function Map:__eq (m) + -- note we explicitly ask deepcompare _not_ to use __eq! + return deepcompare(self,m,true) +end + +function Map:__tostring () + return pretty_write(self,'') +end + +return Map diff --git a/Utils/luarocks/share/lua/5.1/pl/MultiMap.lua b/Utils/luarocks/share/lua/5.1/pl/MultiMap.lua new file mode 100644 index 000000000..f1c430db5 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/MultiMap.lua @@ -0,0 +1,65 @@ +--- MultiMap, a Map which has multiple values per key.
    +-- @class module +-- @name pl.MultiMap + +--[[ +module ('pl.MultiMap') +]] + +local classes = require 'pl.class' +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local List = require 'pl.List' + +local index_by,tsort,concat = tablex.index_by,table.sort,table.concat +local append,extend,slice = List.append,List.extend,List.slice +local append = table.insert +local is_type = utils.is_type + +local class = require 'pl.class' +local Map = require 'pl.Map' + +-- MultiMap is a standard MT +local MultiMap = utils.stdmt.MultiMap + +class(Map,nil,MultiMap) +MultiMap._name = 'MultiMap' + +function MultiMap:_init (t) + if not t then return end + self:update(t) +end + +--- update a MultiMap using a table. +-- @param t either a Multimap or a map-like table. +-- @return the map +function MultiMap:update (t) + utils.assert_arg(1,t,'table') + if Map:class_of(t) then + for k,v in pairs(t) do + self[k] = List() + self[k]:append(v) + end + else + for k,v in pairs(t) do + self[k] = List(v) + end + end +end + +--- add a new value to a key. Setting a nil value removes the key. +-- @param key the key +-- @param val the value +-- @return the map +function MultiMap:set (key,val) + if val == nil then + self[key] = nil + else + if not self[key] then + self[key] = List() + end + self[key]:append(val) + end +end + +return MultiMap diff --git a/Utils/luarocks/share/lua/5.1/pl/OrderedMap.lua b/Utils/luarocks/share/lua/5.1/pl/OrderedMap.lua new file mode 100644 index 000000000..7cc79635a --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/OrderedMap.lua @@ -0,0 +1,150 @@ +--- OrderedMap, a pl.Map which preserves ordering. +-- @class module +-- @name pl.OrderedMap + +--[[ +module ('pl.OrderedMap') +]] + +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local List = require 'pl.List' +local index_by,tsort,concat = tablex.index_by,table.sort,table.concat + +local class = require 'pl.class' +local Map = require 'pl.Map' + +local OrderedMap = class(Map) +OrderedMap._name = 'OrderedMap' + +--- construct an OrderedMap. +-- Will throw an error if the argument is bad. +-- @param t optional initialization table, same as for @{OrderedMap:update} +function OrderedMap:_init (t) + self._keys = List() + if t then + local map,err = self:update(t) + if not map then error(err,2) end + end +end + +local assert_arg,raise = utils.assert_arg,utils.raise + +--- update an OrderedMap using a table.
    +-- If the table is itself an OrderedMap, then its entries will be appended.
    +-- if it s a table of the form {{key1=val1},{key2=val2},...} these will be appended.
    +-- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. +-- @param t a table. +-- @return the map, or nil in case of error +-- @return the error message +function OrderedMap:update (t) + assert_arg(1,t,'table') + if OrderedMap:class_of(t) then + for k,v in t:iter() do + self:set(k,v) + end + elseif #t > 0 then -- an array must contain {key=val} tables + if type(t[1]) == 'table' then + for _,pair in ipairs(t) do + local key,value = next(pair) + if not key then return raise 'empty pair initialization table' end + self:set(key,value) + end + else + return raise 'cannot use an array to initialize an OrderedMap' + end + else + for k,v in pairs(t) do + self:set(k,v) + end + end + return self +end + +--- set the key's value. This key will be appended at the end of the map.
    +-- If the value is nil, then the key is removed. +-- @param key the key +-- @param val the value +-- @return the map +function OrderedMap:set (key,val) + if not self[key] and val ~= nil then -- ensure that keys are unique + self._keys:append(key) + elseif val == nil then -- removing a key-value pair + self._keys:remove_value(key) + end + self[key] = val + return self +end + +--- insert a key/value pair before a given position. +-- Note: if the map already contains the key, then this effectively +-- moves the item to the new position by first removing at the old position. +-- Has no effect if the key does not exist and val is nil +-- @param pos a position starting at 1 +-- @param key the key +-- @param val the value; if nil use the old value +function OrderedMap:insert (pos,key,val) + local oldval = self[key] + val = val or oldval + if oldval then + self._keys:remove_value(key) + end + if val then + self._keys:insert(pos,key) + self[key] = val + end + return self +end + +--- return the keys in order. +-- (Not a copy!) +-- @return List +function OrderedMap:keys () + return self._keys +end + +--- return the values in order. +-- this is relatively expensive. +-- @return List +function OrderedMap:values () + return List(index_by(self,self._keys)) +end + +--- sort the keys. +-- @param cmp a comparison function as for @{table.sort} +-- @return the map +function OrderedMap:sort (cmp) + tsort(self._keys,cmp) + return self +end + +--- iterate over key-value pairs in order. +function OrderedMap:iter () + local i = 0 + local keys = self._keys + local n,idx = #keys + return function() + i = i + 1 + if i > #keys then return nil end + idx = keys[i] + return idx,self[idx] + end +end + +function OrderedMap:__tostring () + local res = {} + for i,v in ipairs(self._keys) do + local val = self[v] + local vs = tostring(val) + if type(val) ~= 'number' then + vs = '"'..vs..'"' + end + res[i] = tostring(v)..'='..vs + end + return '{'..concat(res,',')..'}' +end + +return OrderedMap + + + diff --git a/Utils/luarocks/share/lua/5.1/pl/Set.lua b/Utils/luarocks/share/lua/5.1/pl/Set.lua new file mode 100644 index 000000000..fd6de90ba --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/Set.lua @@ -0,0 +1,127 @@ +---- A Set class. +-- @class module +-- @name pl.Set + +--[[ +module ('pl.Set') +]] +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local stdmt = utils.stdmt +local tmakeset,deepcompare,merge,keys,difference,tupdate = tablex.makeset,tablex.deepcompare,tablex.merge,tablex.keys,tablex.difference,tablex.update +local Map = stdmt.Map +local Set = stdmt.Set +local List = stdmt.List +local class = require 'pl.class' + +-- the Set class -------------------- +class(Map,nil,Set) + +local function makeset (t) + return setmetatable(t,Set) +end + +--- create a set.
    +-- @param t may be a Set, Map or list-like table. +-- @class function +-- @name Set +function Set:_init (t) + local mt = getmetatable(t) + if mt == Set or mt == Map then + for k in pairs(t) do self[k] = true end + else + for _,v in ipairs(t) do self[v] = true end + end +end + +function Set:__tostring () + return '['..self:keys():join ','..']' +end + +--- add a value to a set. +-- @param key a value +function Set:set (key) + self[key] = true +end + +--- remove a value from a set. +-- @param key a value +function Set:unset (key) + self[key] = nil +end + +--- get a list of the values in a set. +-- @class function +-- @name Set:values +Set.values = Map.keys + +--- map a function over the values of a set. +-- @param fn a function +-- @param ... extra arguments to pass to the function. +-- @return a new set +function Set:map (fn,...) + fn = utils.function_arg(1,fn) + local res = {} + for k in pairs(self) do + res[fn(k,...)] = true + end + return makeset(res) +end + +--- union of two sets (also +). +-- @param set another set +-- @return a new set +function Set:union (set) + return merge(self,set,true) +end +Set.__add = Set.union + +--- intersection of two sets (also *). +-- @param set another set +-- @return a new set +function Set:intersection (set) + return merge(self,set,false) +end +Set.__mul = Set.intersection + +--- new set with elements in the set that are not in the other (also -). +-- @param set another set +-- @return a new set +function Set:difference (set) + return difference(self,set,false) +end +Set.__sub = Set.difference + +-- a new set with elements in _either_ the set _or_ other but not both (also ^). +-- @param set another set +-- @return a new set +function Set:symmetric_difference (set) + return difference(self,set,true) +end +Set.__pow = Set.symmetric_difference + +--- is the first set a subset of the second?. +-- @return true or false +function Set:issubset (set) + for k in pairs(self) do + if not set[k] then return false end + end + return true +end +Set.__lt = Set.subset + +--- is the set empty?. +-- @return true or false +function Set:issempty () + return next(self) == nil +end + +--- are the sets disjoint? (no elements in common). +-- Uses naive definition, i.e. that intersection is empty +-- @param set another set +-- @return true or false +function Set:isdisjoint (set) + return self:intersection(set):isempty() +end + +return Set diff --git a/Utils/luarocks/share/lua/5.1/pl/app.lua b/Utils/luarocks/share/lua/5.1/pl/app.lua new file mode 100644 index 000000000..845255094 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/app.lua @@ -0,0 +1,143 @@ +--- Application support functions. +--

    See the Guide +-- @class module +-- @name pl.app + +local io,package,require = _G.io, _G.package, _G.require +local utils = require 'pl.utils' +local path = require 'pl.path' +local lfs = require 'lfs' + + +local app = {} + +local function check_script_name () + if _G.arg == nil then error('no command line args available\nWas this run from a main script?') end + return _G.arg[0] +end + +--- add the current script's path to the Lua module path. +-- Applies to both the source and the binary module paths. It makes it easy for +-- the main file of a multi-file program to access its modules in the same directory. +-- `base` allows these modules to be put in a specified subdirectory, to allow for +-- cleaner deployment and resolve potential conflicts between a script name and its +-- library directory. +-- @param base optional base directory. +-- @return the current script's path with a trailing slash +function app.require_here (base) + local p = path.dirname(check_script_name()) + if not path.isabs(p) then + p = path.join(lfs.currentdir(),p) + end + if p:sub(-1,-1) ~= path.sep then + p = p..path.sep + end + if base then + p = p..base..path.sep + end + local so_ext = path.is_windows and 'dll' or 'so' + local lsep = package.path:find '^;' and '' or ';' + local csep = package.cpath:find '^;' and '' or ';' + package.path = ('%s?.lua;%s?%sinit.lua%s%s'):format(p,p,path.sep,lsep,package.path) + package.cpath = ('%s?.%s%s%s'):format(p,so_ext,csep,package.cpath) + return p +end + +--- return a suitable path for files private to this application. +-- These will look like '~/.SNAME/file', with '~' as with expanduser and +-- SNAME is the name of the script without .lua extension. +-- @param file a filename (w/out path) +-- @return a full pathname, or nil +-- @return 'cannot create' error +function app.appfile (file) + local sname = path.basename(check_script_name()) + local name,ext = path.splitext(sname) + local dir = path.join(path.expanduser('~'),'.'..name) + if not path.isdir(dir) then + local ret = lfs.mkdir(dir) + if not ret then return utils.raise ('cannot create '..dir) end + end + return path.join(dir,file) +end + +--- return string indicating operating system. +-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux') +function app.platform() + if path.is_windows then + return 'Windows' + else + local f = io.popen('uname') + local res = f:read() + if res == 'Darwin' then res = 'OSX' end + f:close() + return res + end +end + +--- parse command-line arguments into flags and parameters. +-- Understands GNU-style command-line flags; short (-f) and long (--flag). +-- These may be given a value with either '=' or ':' (-k:2,--alpha=3.2,-n2); +-- note that a number value can be given without a space. +-- Multiple short args can be combined like so: (-abcd). +-- @param args an array of strings (default is the global 'arg') +-- @param flags_with_values any flags that take values, e.g. {out=true} +-- @return a table of flags (flag=value pairs) +-- @return an array of parameters +-- @raise if args is nil, then the global `args` must be available! +function app.parse_args (args,flags_with_values) + if not args then + args = _G.arg + if not args then error "Not in a main program: 'arg' not found" end + end + flags_with_values = flags_with_values or {} + local _args = {} + local flags = {} + local i = 1 + while i <= #args do + local a = args[i] + local v = a:match('^-(.+)') + local is_long + if v then -- we have a flag + if v:find '^-' then + is_long = true + v = v:sub(2) + end + if flags_with_values[v] then + if i == #_args or args[i+1]:find '^-' then + return utils.raise ("no value for '"..v.."'") + end + flags[v] = args[i+1] + i = i + 1 + else + -- a value can be indicated with = or : + local var,val = utils.splitv (v,'[=:]') + var = var or v + val = val or true + if not is_long then + if #var > 1 then + if var:find '.%d+' then -- short flag, number value + val = var:sub(2) + var = var:sub(1,1) + else -- multiple short flags + for i = 1,#var do + flags[var:sub(i,i)] = true + end + val = nil -- prevents use of var as a flag below + end + else -- single short flag (can have value, defaults to true) + val = val or true + end + end + if val then + flags[var] = val + end + end + else + _args[#_args+1] = a + end + i = i + 1 + end + return flags,_args +end + +return app diff --git a/Utils/luarocks/share/lua/5.1/pl/array2d.lua b/Utils/luarocks/share/lua/5.1/pl/array2d.lua new file mode 100644 index 000000000..e178df975 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/array2d.lua @@ -0,0 +1,391 @@ +--- Operations on two-dimensional arrays. +-- @class module +-- @name pl.array2d + +local require, type,tonumber,assert,tostring,io,ipairs,string,table = + _G.require, _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table +local ops = require 'pl.operator' +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' + +local imap,tmap,reduce,keys,tmap2,tset,index_by = tablex.imap,tablex.map,tablex.reduce,tablex.keys,tablex.map2,tablex.set,tablex.index_by +local remove = table.remove +local perm = require 'pl.permute' +local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg +local byte = string.byte +local stdout = io.stdout + +--[[ +module ('pl.array2d',utils._module) +]] + +local array2d = {} + +--- extract a column from the 2D array. +-- @param a 2d array +-- @param key an index or key +-- @return 1d array +function array2d.column (a,key) + assert_arg(1,a,'table') + return imap(ops.index,a,key) +end +local column = array2d.column + +--- map a function over a 2D array +-- @param f a function of at least one argument +-- @param a 2d array +-- @param arg an optional extra argument to be passed to the function. +-- @return 2d array +function array2d.map (f,a,arg) + assert_arg(1,a,'table') + f = utils.function_arg(1,f) + return imap(function(row) return imap(f,row,arg) end, a) +end + +--- reduce the rows using a function. +-- @param f a binary function +-- @param a 2d array +-- @return 1d array +-- @see pl.tablex.reduce +function array2d.reduce_rows (f,a) + assert_arg(1,a,'table') + return tmap(function(row) return reduce(f,row) end, a) +end + + + +--- reduce the columns using a function. +-- @param f a binary function +-- @param a 2d array +-- @return 1d array +-- @see pl.tablex.reduce +function array2d.reduce_cols (f,a) + assert_arg(1,a,'table') + return tmap(function(c) return reduce(f,column(a,c)) end, keys(a[1])) +end + +--- reduce a 2D array into a scalar, using two operations. +-- @param opc operation to reduce the final result +-- @param opr operation to reduce the rows +-- @param a 2D array +function array2d.reduce2 (opc,opr,a) + assert_arg(3,a,'table') + local tmp = array2d.reduce_rows(opr,a) + return reduce(opc,tmp) +end + +local function dimension (t) + return type(t[1])=='table' and 2 or 1 +end + +--- map a function over two arrays. +-- They can be both or either 2D arrays +-- @param f function of at least two arguments +-- @param ad order of first array +-- @param bd order of second array +-- @param a 1d or 2d array +-- @param b 1d or 2d array +-- @param arg optional extra argument to pass to function +-- @return 2D array, unless both arrays are 1D +function array2d.map2 (f,ad,bd,a,b,arg) + assert_arg(1,a,'table') + assert_arg(2,b,'table') + f = utils.function_arg(1,f) + --local ad,bd = dimension(a),dimension(b) + if ad == 1 and bd == 2 then + return imap(function(row) + return tmap2(f,a,row,arg) + end, b) + elseif ad == 2 and bd == 1 then + return imap(function(row) + return tmap2(f,row,b,arg) + end, a) + elseif ad == 1 and bd == 1 then + return tmap2(f,a,b) + elseif ad == 2 and bd == 2 then + return tmap2(function(rowa,rowb) + return tmap2(f,rowa,rowb,arg) + end, a,b) + end +end + +--- cartesian product of two 1d arrays. +-- @param f a function of 2 arguments +-- @param t1 a 1d table +-- @param t2 a 1d table +-- @return 2d table +-- @usage product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}} +function array2d.product (f,t1,t2) + f = utils.function_arg(1,f) + assert_arg(2,t1,'table') + assert_arg(3,t2,'table') + local res, map = {}, tablex.map + for i,v in ipairs(t2) do + res[i] = map(f,t1,v) + end + return res +end + +--- flatten a 2D array. +-- (this goes over columns first.) +-- @param t 2d table +-- @return a 1d table +-- @usage flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6} +function array2d.flatten (t) + local res = {} + local k = 1 + for _,a in ipairs(t) do -- for all rows + for i = 1,#a do + res[k] = a[i] + k = k + 1 + end + end + return res +end + +--- swap two rows of an array. +-- @param t a 2d array +-- @param i1 a row index +-- @param i2 a row index +function array2d.swap_rows (t,i1,i2) + assert_arg(1,t,'table') + t[i1],t[i2] = t[i2],t[i1] +end + +--- swap two columns of an array. +-- @param t a 2d array +-- @param j1 a column index +-- @param j2 a column index +function array2d.swap_cols (t,j1,j2) + assert_arg(1,t,'table') + for i = 1,#t do + local row = t[i] + row[j1],row[j2] = row[j2],row[j1] + end +end + +--- extract the specified rows. +-- @param t 2d array +-- @param ridx a table of row indices +function array2d.extract_rows (t,ridx) + return index_by(t,ridx) +end + +--- extract the specified columns. +-- @param t 2d array +-- @param cidx a table of column indices +function array2d.extract_cols (t,cidx) + assert_arg(1,t,'table') + for i = 1,#t do + t[i] = index_by(t[i],cidx) + end +end + +--- remove a row from an array. +-- @class function +-- @name array2d.remove_row +-- @param t a 2d array +-- @param i a row index +array2d.remove_row = remove + +--- remove a column from an array. +-- @param t a 2d array +-- @param j a column index +function array2d.remove_col (t,j) + assert_arg(1,t,'table') + for i = 1,#t do + remove(t[i],j) + end +end + +local Ai = byte 'A' + +local function _parse (s) + local c,r + if s:sub(1,1) == 'R' then + r,c = s:match 'R(%d+)C(%d+)' + r,c = tonumber(r),tonumber(c) + else + c,r = s:match '(.)(.)' + c = byte(c) - byte 'A' + 1 + r = tonumber(r) + end + assert(c ~= nil and r ~= nil,'bad cell specifier: '..s) + return r,c +end + +--- parse a spreadsheet range. +-- The range can be specified either as 'A1:B2' or 'R1C1:R2C2'; +-- a special case is a single element (e.g 'A1' or 'R1C1') +-- @param s a range. +-- @return start col +-- @return start row +-- @return end col +-- @return end row +function array2d.parse_range (s) + if s:find ':' then + local start,finish = splitv(s,':') + local i1,j1 = _parse(start) + local i2,j2 = _parse(finish) + return i1,j1,i2,j2 + else -- single value + local i,j = _parse(s) + return i,j + end +end + +--- get a slice of a 2D array using spreadsheet range notation. @see parse_range +-- @param t a 2D array +-- @param rstr range expression +-- @return a slice +-- @see array2d.parse_range +-- @see array2d.slice +function array2d.range (t,rstr) + assert_arg(1,t,'table') + local i1,j1,i2,j2 = array2d.parse_range(rstr) + if i2 then + return array2d.slice(t,i1,j1,i2,j2) + else -- single value + return t[i1][j1] + end +end + +local function default_range (t,i1,j1,i2,j2) + assert(t and type(t)=='table','not a table') + i1,j1 = i1 or 1, j1 or 1 + i2,j2 = i2 or #t, j2 or #t[1] + return i1,j1,i2,j2 +end + +--- get a slice of a 2D array. Note that if the specified range has +-- a 1D result, the rank of the result will be 1. +-- @param t a 2D array +-- @param i1 start row (default 1) +-- @param j1 start col (default 1) +-- @param i2 end row (default N) +-- @param j2 end col (default M) +-- @return an array, 2D in general but 1D in special cases. +function array2d.slice (t,i1,j1,i2,j2) + assert_arg(1,t,'table') + i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) + local res = {} + for i = i1,i2 do + local val + local row = t[i] + if j1 == j2 then + val = row[j1] + else + val = {} + for j = j1,j2 do + val[#val+1] = row[j] + end + end + res[#res+1] = val + end + if i1 == i2 then res = res[1] end + return res +end + +--- set a specified range of an array to a value. +-- @param t a 2D array +-- @param value the value +-- @param i1 start row (default 1) +-- @param j1 start col (default 1) +-- @param i2 end row (default N) +-- @param j2 end col (default M) +function array2d.set (t,value,i1,j1,i2,j2) + i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) + for i = i1,i2 do + tset(t[i],value) + end +end + +--- write a 2D array to a file. +-- @param t a 2D array +-- @param f a file object (default stdout) +-- @param fmt a format string (default is just to use tostring) +-- @param i1 start row (default 1) +-- @param j1 start col (default 1) +-- @param i2 end row (default N) +-- @param j2 end col (default M) +function array2d.write (t,f,fmt,i1,j1,i2,j2) + assert_arg(1,t,'table') + f = f or stdout + local rowop + if fmt then + rowop = function(row,j) fprintf(f,fmt,row[j]) end + else + rowop = function(row,j) f:write(tostring(row[j]),' ') end + end + local function newline() + f:write '\n' + end + array2d.forall(t,rowop,newline,i1,j1,i2,j2) +end + +--- perform an operation for all values in a 2D array. +-- @param t 2D array +-- @param row_op function to call on each value +-- @param end_row_op function to call at end of each row +-- @param i1 start row (default 1) +-- @param j1 start col (default 1) +-- @param i2 end row (default N) +-- @param j2 end col (default M) +function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2) + assert_arg(1,t,'table') + i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2) + for i = i1,i2 do + local row = t[i] + for j = j1,j2 do + row_op(row,j) + end + if end_row_op then end_row_op(i) end + end +end + +--- iterate over all elements in a 2D array, with optional indices. +-- @param a 2D array +-- @param indices with indices (default false) +-- @param i1 start row (default 1) +-- @param j1 start col (default 1) +-- @param i2 end row (default N) +-- @param j2 end col (default M) +-- @return either value or i,j,value depending on indices +function array2d.iter (a,indices,i1,j1,i2,j2) + assert_arg(1,a,'table') + local norowset = not (i2 and j2) + i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2) + local n,i,j = i2-i1+1,i1-1,j1-1 + local row,nr = nil,0 + local onr = j2 - j1 + 1 + return function() + j = j + 1 + if j > nr then + j = j1 + i = i + 1 + if i > i2 then return nil end + row = a[i] + nr = norowset and #row or onr + end + if indices then + return i,j,row[j] + else + return row[j] + end + end +end + +function array2d.columns (a) + assert_arg(1,a,'table') + local n = a[1][1] + local i = 0 + return function() + i = i + 1 + if i > n then return nil end + return column(a,i) + end +end + +return array2d + + diff --git a/Utils/luarocks/share/lua/5.1/pl/class.lua b/Utils/luarocks/share/lua/5.1/pl/class.lua new file mode 100644 index 000000000..5cfbf991d --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/class.lua @@ -0,0 +1,155 @@ +--- Provides a reuseable and convenient framework for creating classes in Lua. +-- Two possible notations:
    B = class(A) or class.B(A) .
    +--

    The latter form creates a named class.

    +-- See the Guide for further discussion +-- @module pl.class + +local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type = + _G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type +-- this trickery is necessary to prevent the inheritance of 'super' and +-- the resulting recursive call problems. +local function call_ctor (c,obj,...) + -- nice alias for the base class ctor + local base = rawget(c,'_base') + if base then obj.super = rawget(base,'_init') end + local res = c._init(obj,...) + obj.super = nil + return res +end + +local function is_a(self,klass) + local m = getmetatable(self) + if not m then return false end --*can't be an object! + while m do + if m == klass then return true end + m = rawget(m,'_base') + end + return false +end + +local function class_of(klass,obj) + if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end + return klass.is_a(obj,klass) +end + +local function _class_tostring (obj) + local mt = obj._class + local name = rawget(mt,'_name') + setmetatable(obj,nil) + local str = tostring(obj) + setmetatable(obj,mt) + if name then str = name ..str:gsub('table','') end + return str +end + +local function tupdate(td,ts) + for k,v in pairs(ts) do + td[k] = v + end +end + +local function _class(base,c_arg,c) + c = c or {} -- a new class instance, which is the metatable for all objects of this type + -- the class will be the metatable for all its objects, + -- and they will look up their methods in it. + local mt = {} -- a metatable for the class instance + + if type(base) == 'table' then + -- our new class is a shallow copy of the base class! + tupdate(c,base) + c._base = base + -- inherit the 'not found' handler, if present + if rawget(c,'_handler') then mt.__index = c._handler end + elseif base ~= nil then + error("must derive from a table type",3) + end + + c.__index = c + setmetatable(c,mt) + c._init = nil + + if base and rawget(base,'_class_init') then + base._class_init(c,c_arg) + end + + -- expose a ctor which can be called by () + mt.__call = function(class_tbl,...) + local obj = {} + setmetatable(obj,c) + + if rawget(c,'_init') then -- explicit constructor + local res = call_ctor(c,obj,...) + if res then -- _if_ a ctor returns a value, it becomes the object... + obj = res + setmetatable(obj,c) + end + elseif base and rawget(base,'_init') then -- default constructor + -- make sure that any stuff from the base class is initialized! + call_ctor(base,obj,...) + end + + if base and rawget(base,'_post_init') then + base._post_init(obj) + end + + if not rawget(c,'__tostring') then + c.__tostring = _class_tostring + end + return obj + end + -- Call Class.catch to set a handler for methods/properties not found in the class! + c.catch = function(handler) + c._handler = handler + mt.__index = handler + end + c.is_a = is_a + c.class_of = class_of + c._class = c + -- any object can have a specified delegate which is called with unrecognized methods + -- if _handler exists and obj[key] is nil, then pass onto handler! + c.delegate = function(self,obj) + mt.__index = function(tbl,key) + local method = obj[key] + if method then + return function(self,...) + return method(obj,...) + end + elseif self._handler then + return self._handler(tbl,key) + end + end + end + return c +end + +--- create a new class, derived from a given base class.
    +-- Supporting two class creation syntaxes: +-- either Name = class(base) or class.Name(base) +-- @class function +-- @name class +-- @param base optional base class +-- @param c_arg optional parameter to class ctor +-- @param c optional table to be used as class +local class +class = setmetatable({},{ + __call = function(fun,...) + return _class(...) + end, + __index = function(tbl,key) + if key == 'class' then + io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n') + return class + end + local env = _G + return function(...) + local c = _class(...) + c._name = key + rawset(env,key,c) + return c + end + end +}) + + +return class + diff --git a/Utils/luarocks/share/lua/5.1/pl/comprehension.lua b/Utils/luarocks/share/lua/5.1/pl/comprehension.lua new file mode 100644 index 000000000..9765bbf87 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/comprehension.lua @@ -0,0 +1,288 @@ +--- List comprehensions implemented in Lua.

    +-- +-- See the wiki page +--

    +--   local C= require 'pl.comprehension' . new()
    +--
    +--    C ('x for x=1,10') ()
    +--    ==> {1,2,3,4,5,6,7,8,9,10}
    +--    C 'x^2 for x=1,4' ()
    +--    ==> {1,4,9,16}
    +--    C '{x,x^2} for x=1,4' ()
    +--    ==> {{1,1},{2,4},{3,9},{4,16}}
    +--    C '2*x for x' {1,2,3}
    +--    ==> {2,4,6}
    +--    dbl = C '2*x for x'
    +--    dbl {10,20,30}
    +--    ==> {20,40,60}
    +--    C 'x for x if x % 2 == 0' {1,2,3,4,5}
    +--    ==> {2,4}
    +--    C '{x,y} for x = 1,2 for y = 1,2' ()
    +--    ==> {{1,1},{1,2},{2,1},{2,2}}
    +--    C '{x,y} for x for y' ({1,2},{10,20})
    +--    ==> {{1,10},{1,20},{2,10},{2,20}}
    +--   assert(C 'sum(x^2 for x)' {2,3,4} == 2^2+3^2+4^2)
    +-- 
    +-- +--

    (c) 2008 David Manura. Licensed under the same terms as Lua (MIT license). +--

    -- See the Guide +-- @class module +-- @name pl.comprehension + +local utils = require 'pl.utils' + +--~ local _VERSION, assert, getfenv, ipairs, load, math, pcall, require, setmetatable, table, tonumber = +--~ _G._VERSION, _G.assert, _G.getfenv, _G.ipairs, _G.load, _G.math, _G.pcall, _G.require, _G.setmetatable, _G.table, _G.tonumber + +local status,lb = pcall(require, "pl.luabalanced") +if not status then + lb = require 'luabalanced' +end + +local math_max = math.max +local table_concat = table.concat + +-- fold operations +-- http://en.wikipedia.org/wiki/Fold_(higher-order_function) +local ops = { + list = {init=' {} ', accum=' __result[#__result+1] = (%s) '}, + table = {init=' {} ', accum=' local __k, __v = %s __result[__k] = __v '}, + sum = {init=' 0 ', accum=' __result = __result + (%s) '}, + min = {init=' nil ', accum=' local __tmp = %s ' .. + ' if __result then if __tmp < __result then ' .. + '__result = __tmp end else __result = __tmp end '}, + max = {init=' nil ', accum=' local __tmp = %s ' .. + ' if __result then if __tmp > __result then ' .. + '__result = __tmp end else __result = __tmp end '}, +} + + +-- Parses comprehension string expr. +-- Returns output expression list string, array of for types +-- ('=', 'in' or nil) , array of input variable name +-- strings , array of input variable value strings +-- , array of predicate expression strings , +-- operation name string , and number of placeholder +-- parameters . +-- +-- The is equivalent to the mathematical set-builder notation: +-- +-- { | in , } +-- +-- @usage "x^2 for x" -- array values +-- @usage "x^2 for x=1,10,2" -- numeric for +-- @usage "k^v for k,v in pairs(_1)" -- iterator for +-- @usage "(x+y)^2 for x for y if x > y" -- nested +-- +local function parse_comprehension(expr) + local t = {} + local pos = 1 + + -- extract opname (if exists) + local opname + local tok, post = expr:match('^%s*([%a_][%w_]*)%s*%(()', pos) + local pose = #expr + 1 + if tok then + local tok2, posb = lb.match_bracketed(expr, post-1) + assert(tok2, 'syntax error') + if expr:match('^%s*$', posb) then + opname = tok + pose = posb - 1 + pos = post + end + end + opname = opname or "list" + + -- extract out expression list + local out; out, pos = lb.match_explist(expr, pos) + assert(out, "syntax error: missing expression list") + out = table_concat(out, ', ') + + -- extract "for" clauses + local fortypes = {} + local invarlists = {} + local invallists = {} + while 1 do + local post = expr:match('^%s*for%s+()', pos) + if not post then break end + pos = post + + -- extract input vars + local iv; iv, pos = lb.match_namelist(expr, pos) + assert(#iv > 0, 'syntax error: zero variables') + for _,ident in ipairs(iv) do + assert(not ident:match'^__', + "identifier " .. ident .. " may not contain __ prefix") + end + invarlists[#invarlists+1] = iv + + -- extract '=' or 'in' (optional) + local fortype, post = expr:match('^(=)%s*()', pos) + if not fortype then fortype, post = expr:match('^(in)%s+()', pos) end + if fortype then + pos = post + -- extract input value range + local il; il, pos = lb.match_explist(expr, pos) + assert(#il > 0, 'syntax error: zero expressions') + assert(fortype ~= '=' or #il == 2 or #il == 3, + 'syntax error: numeric for requires 2 or three expressions') + fortypes[#invarlists] = fortype + invallists[#invarlists] = il + else + fortypes[#invarlists] = false + invallists[#invarlists] = false + end + end + assert(#invarlists > 0, 'syntax error: missing "for" clause') + + -- extract "if" clauses + local preds = {} + while 1 do + local post = expr:match('^%s*if%s+()', pos) + if not post then break end + pos = post + local pred; pred, pos = lb.match_expression(expr, pos) + assert(pred, 'syntax error: predicated expression not found') + preds[#preds+1] = pred + end + + -- extract number of parameter variables (name matching "_%d+") + local stmp = ''; lb.gsub(expr, function(u, sin) -- strip comments/strings + if u == 'e' then stmp = stmp .. ' ' .. sin .. ' ' end + end) + local max_param = 0; stmp:gsub('[%a_][%w_]*', function(s) + local s = s:match('^_(%d+)$') + if s then max_param = math_max(max_param, tonumber(s)) end + end) + + if pos ~= pose then + assert(false, "syntax error: unrecognized " .. expr:sub(pos)) + end + + --DEBUG: + --print('----\n', string.format("%q", expr), string.format("%q", out), opname) + --for k,v in ipairs(invarlists) do print(k,v, invallists[k]) end + --for k,v in ipairs(preds) do print(k,v) end + + return out, fortypes, invarlists, invallists, preds, opname, max_param +end + + +-- Create Lua code string representing comprehension. +-- Arguments are in the form returned by parse_comprehension. +local function code_comprehension( + out, fortypes, invarlists, invallists, preds, opname, max_param +) + local op = assert(ops[opname]) + local code = op.accum:gsub('%%s', out) + + for i=#preds,1,-1 do local pred = preds[i] + code = ' if ' .. pred .. ' then ' .. code .. ' end ' + end + for i=#invarlists,1,-1 do + if not fortypes[i] then + local arrayname = '__in' .. i + local idx = '__idx' .. i + code = + ' for ' .. idx .. ' = 1, #' .. arrayname .. ' do ' .. + ' local ' .. invarlists[i][1] .. ' = ' .. arrayname .. '['..idx..'] ' .. + code .. ' end ' + else + code = + ' for ' .. + table_concat(invarlists[i], ', ') .. + ' ' .. fortypes[i] .. ' ' .. + table_concat(invallists[i], ', ') .. + ' do ' .. code .. ' end ' + end + end + code = ' local __result = ( ' .. op.init .. ' ) ' .. code + return code +end + + +-- Convert code string represented by code_comprehension +-- into Lua function. Also must pass ninputs = #invarlists, +-- max_param, and invallists (from parse_comprehension). +-- Uses environment env. +local function wrap_comprehension(code, ninputs, max_param, invallists, env) + assert(ninputs > 0) + local ts = {} + for i=1,max_param do + ts[#ts+1] = '_' .. i + end + for i=1,ninputs do + if not invallists[i] then + local name = '__in' .. i + ts[#ts+1] = name + end + end + if #ts > 0 then + code = ' local ' .. table_concat(ts, ', ') .. ' = ... ' .. code + end + code = code .. ' return __result ' + --print('DEBUG:', code) + local f, err = utils.load(code,'tmp','t',env) + if not f then assert(false, err .. ' with generated code ' .. code) end + return f +end + + +-- Build Lua function from comprehension string. +-- Uses environment env. +local function build_comprehension(expr, env) + local out, fortypes, invarlists, invallists, preds, opname, max_param + = parse_comprehension(expr) + local code = code_comprehension( + out, fortypes, invarlists, invallists, preds, opname, max_param) + local f = wrap_comprehension(code, #invarlists, max_param, invallists, env) + return f +end + + +-- Creates new comprehension cache. +-- Any list comprehension function created are set to the environment +-- env (defaults to caller of new). +local function new(env) + -- Note: using a single global comprehension cache would have had + -- security implications (e.g. retrieving cached functions created + -- in other environments). + -- The cache lookup function could have instead been written to retrieve + -- the caller's environment, lookup up the cache private to that + -- environment, and then looked up the function in that cache. + -- That would avoid the need for this call to + -- explicitly manage caches; however, that might also have an undue + -- performance penalty. + + if not env then + env = getfenv(2) + end + + local mt = {} + local cache = setmetatable({}, mt) + + -- Index operator builds, caches, and returns Lua function + -- corresponding to comprehension expression string. + -- + -- Example: f = comprehension['x^2 for x'] + -- + function mt:__index(expr) + local f = build_comprehension(expr, env) + self[expr] = f -- cache + return f + end + + -- Convenience syntax. + -- Allows comprehension 'x^2 for x' instead of comprehension['x^2 for x']. + mt.__call = mt.__index + + cache.new = new + + return cache +end + + +local comprehension = {} +comprehension.new = new + +return comprehension diff --git a/Utils/luarocks/share/lua/5.1/pl/config.lua b/Utils/luarocks/share/lua/5.1/pl/config.lua new file mode 100644 index 000000000..bd6b89781 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/config.lua @@ -0,0 +1,169 @@ +--- Reads configuration files into a Lua table.

    +-- Understands INI files, classic Unix config files, and simple +-- delimited columns of values.

    +--

    +--    # test.config
    +--    # Read timeout in seconds
    +--    read.timeout=10
    +--    # Write timeout in seconds
    +--    write.timeout=5
    +--    #acceptable ports
    +--    ports = 1002,1003,1004
    +--
    +--        -- readconfig.lua
    +--    require 'pl'
    +--    local t = config.read 'test.config'
    +--    print(pretty.write(t))
    +--
    +--    ### output #####
    +--   {
    +--      ports = {
    +--        1002,
    +--        1003,
    +--        1004
    +--      },
    +--      write_timeout = 5,
    +--      read_timeout = 10
    +--    }
    +-- 
    +-- See the Guide for further discussion +-- @class module +-- @name pl.config + +local type,tonumber,ipairs,io, table = _G.type,_G.tonumber,_G.ipairs,_G.io,_G.table + +local function split(s,re) + local res = {} + local t_insert = table.insert + re = '[^'..re..']+' + for k in s:gmatch(re) do t_insert(res,k) end + return res +end + +local function strip(s) + return s:gsub('^%s+',''):gsub('%s+$','') +end + +local function strip_quotes (s) + return s:gsub("['\"](.*)['\"]",'%1') +end + +local config = {} + +--- like io.lines(), but allows for lines to be continued with '\'. +-- @param file a file-like object (anything where read() returns the next line) or a filename. +-- Defaults to stardard input. +-- @return an iterator over the lines, or nil +-- @return error 'not a file-like object' or 'file is nil' +function config.lines(file) + local f,openf,err + local line = '' + if type(file) == 'string' then + f,err = io.open(file,'r') + if not f then return nil,err end + openf = true + else + f = file or io.stdin + if not file.read then return nil, 'not a file-like object' end + end + if not f then return nil, 'file is nil' end + return function() + local l = f:read() + while l do + -- does the line end with '\'? + local i = l:find '\\%s*$' + if i then -- if so, + line = line..l:sub(1,i-1) + elseif line == '' then + return l + else + l = line..l + line = '' + return l + end + l = f:read() + end + if openf then f:close() end + end +end + +--- read a configuration file into a table +-- @param file either a file-like object or a string, which must be a filename +-- @param cnfg a configuration table that may contain these fields: +--
      +--
    • variablilize make names into valid Lua identifiers (default true)
    • +--
    • convert_numbers try to convert values into numbers (default true)
    • +--
    • trim_space ensure that there is no starting or trailing whitespace with values (default true)
    • +--
    • trim_quotes remove quotes from strings (default false)
    • +--
    • list_delim delimiter to use when separating columns (default ',')
    • +--
    +-- @return a table containing items, or nil +-- @return error message (same as @{config.lines} +function config.read(file,cnfg) + local f,openf,err + cnfg = cnfg or {} + local function check_cnfg (var,def) + local val = cnfg[var] + if val == nil then return def else return val end + end + local t = {} + local top_t = t + local variablilize = check_cnfg ('variabilize',true) + local list_delim = check_cnfg('list_delim',',') + local convert_numbers = check_cnfg('convert_numbers',true) + local trim_space = check_cnfg('trim_space',true) + local trim_quotes = check_cnfg('trim_quotes',false) + local ignore_assign = check_cnfg('ignore_assign',false) + + local function process_name(key) + if variablilize then + key = key:gsub('[^%w]','_') + end + return key + end + + local function process_value(value) + if list_delim and value:find(list_delim) then + value = split(value,list_delim) + for i,v in ipairs(value) do + value[i] = process_value(v) + end + elseif convert_numbers and value:find('^[%d%+%-]') then + local val = tonumber(value) + if val then value = val end + end + if type(value) == 'string' then + if trim_space then value = strip(value) end + if trim_quotes then value = strip_quotes(value) end + end + return value + end + + local iter,err = config.lines(file) + if not iter then return nil,err end + for line in iter do + -- strips comments + local ci = line:find('%s*[#;]') + if ci then line = line:sub(1,ci-1) end + -- and ignore blank lines + if line:find('^%s*$') then + elseif line:find('^%[') then -- section! + local section = process_name(line:match('%[([^%]]+)%]')) + t = top_t + t[section] = {} + t = t[section] + else + local i1,i2 = line:find('%s*=%s*') + if i1 and not ignore_assign then -- key,value assignment + local key = process_name(line:sub(1,i1-1)) + local value = process_value(line:sub(i2+1)) + t[key] = value + else -- a plain list of values... + t[#t+1] = process_value(line) + end + end + end + return top_t +end + +return config diff --git a/Utils/luarocks/share/lua/5.1/pl/data.lua b/Utils/luarocks/share/lua/5.1/pl/data.lua new file mode 100644 index 000000000..931c92692 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/data.lua @@ -0,0 +1,588 @@ +--- Reading and querying simple tabular data. +--
    +-- data.read 'test.txt'
    +-- ==> {{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
    +-- 
    +-- Provides a way of creating basic SQL-like queries. +--
    +--    require 'pl'
    +--    local d = data.read('xyz.txt')
    +--    local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
    +--    for x,y,z in q do
    +--        print(x,y,z)
    +--    end
    +-- 
    +--

    See the Guide +-- @class module +-- @name pl.data + +local utils = require 'pl.utils' +local _DEBUG = rawget(_G,'_DEBUG') + +local patterns,function_arg,usplit = utils.patterns,utils.function_arg,utils.split +local append,concat = table.insert,table.concat +local gsub = string.gsub +local io = io +local _G,print,loadstring,type,tonumber,ipairs,setmetatable,pcall,error,setfenv = _G,print,loadstring,type,tonumber,ipairs,setmetatable,pcall,error,setfenv + +--[[ +module ('pl.data',utils._module) +]] + +local data = {} + +local parse_select + +local function count(s,chr) + chr = utils.escape(chr) + local _,cnt = s:gsub(chr,' ') + return cnt +end + +local function rstrip(s) + return s:gsub('%s+$','') +end + +local function make_list(l) + return setmetatable(l,utils.stdmt.List) +end + +local function split(s,delim) + return make_list(usplit(s,delim)) +end + +local function map(fun,t) + local res = {} + for i = 1,#t do + append(res,fun(t[i])) + end + return res +end + +local function find(t,v) + for i = 1,#t do + if v == t[i] then return i end + end +end + +local DataMT = { + column_by_name = function(self,name) + if type(name) == 'number' then + name = '$'..name + end + local arr = {} + for res in data.query(self,name) do + append(arr,res) + end + return make_list(arr) + end, + + copy_select = function(self,condn) + condn = parse_select(condn,self) + local iter = data.query(self,condn) + local res = {} + local row = make_list{iter()} + while #row > 0 do + append(res,row) + row = make_list{iter()} + end + res.delim = self.delim + return data.new(res,split(condn.fields,',')) + end, + + column_names = function(self) + return self.fieldnames + end, +} +DataMT.__index = DataMT + +--- return a particular column as a list of values (Method).
    +-- @param name either name of column, or numerical index. +-- @class function +-- @name Data.column_by_name + +--- return a query iterator on this data object (Method).
    +-- @param condn the query expression +-- @class function +-- @name Data.select +-- @see data.query + +--- return a new data object based on this query (Method).
    +-- @param condn the query expression +-- @class function +-- @name Data.copy_select + +--- return the field names of this data object (Method).
    +-- @class function +-- @name Data.column_names + +--- write out a row (Method).
    +-- @param f file-like object +-- @class function +-- @name Data.write_row + +--- write data out to file(Method).
    +-- @param f file-like object +-- @class function +-- @name Data.write + + +-- [guessing delimiter] We check for comma, tab and spaces in that order. +-- [issue] any other delimiters to be checked? +local delims = {',','\t',' ',';'} + +local function guess_delim (line) + for _,delim in ipairs(delims) do + if count(line,delim) > 0 then + return delim == ' ' and '%s+' or delim + end + end + return ' ' +end + +-- [file parameter] If it's a string, we try open as a filename. If nil, then +-- either stdin or stdout depending on the mode. Otherwise, check if this is +-- a file-like object (implements read or write depending) +local function open_file (f,mode) + local opened, err + local reading = mode == 'r' + if type(f) == 'string' then + if f == 'stdin' then + f = io.stdin + elseif f == 'stdout' then + f = io.stdout + else + f,err = io.open(f,mode) + if not f then return nil,err end + opened = true + end + end + if f and ((reading and not f.read) or (not reading and not f.write)) then + return nil, "not a file-like object" + end + return f,nil,opened +end + +local function all_n () + +end + +--- read a delimited file in a Lua table. +-- By default, attempts to treat first line as separated list of fieldnames. +-- @param file a filename or a file-like object (default stdin) +-- @param cnfg options table: can override delim (a string pattern), fieldnames (a list), +-- specify no_convert (default is to convert), numfields (indices of columns known +-- to be numbers) and thousands_dot (thousands separator in Excel CSV is '.') +function data.read(file,cnfg) + local convert,err,opened + local D = {} + if not cnfg then cnfg = {} end + local f,err,opened = open_file(file,'r') + if not f then return nil, err end + local thousands_dot = cnfg.thousands_dot + + local function try_tonumber(x) + if thousands_dot then x = x:gsub('%.(...)','%1') end + return tonumber(x) + end + + local line = f:read() + if not line then return nil, "empty file" end + -- first question: what is the delimiter? + D.delim = cnfg.delim and cnfg.delim or guess_delim(line) + local delim = D.delim + local collect_end = cnfg.last_field_collect + local numfields = cnfg.numfields + -- some space-delimited data starts with a space. This should not be a column, + -- although it certainly would be for comma-separated, etc. + local strip + if delim == '%s+' and line:find(delim) == 1 then + strip = function(s) return s:gsub('^%s+','') end + line = strip(line) + end + -- first line will usually be field names. Unless fieldnames are specified, + -- we check if it contains purely numerical values for the case of reading + -- plain data files. + if not cnfg.fieldnames then + local fields = split(line,delim) + local nums = map(tonumber,fields) + if #nums == #fields then + convert = tonumber + append(D,nums) + numfields = {} + for i = 1,#nums do numfields[i] = i end + else + cnfg.fieldnames = fields + end + line = f:read() + if strip then line = strip(line) end + elseif type(cnfg.fieldnames) == 'string' then + cnfg.fieldnames = split(cnfg.fieldnames,delim) + end + -- at this point, the column headers have been read in. If the first + -- row consisted of numbers, it has already been added to the dataset. + if cnfg.fieldnames then + D.fieldnames = cnfg.fieldnames + -- [conversion] unless @no_convert, we need the numerical field indices + -- of the first data row. Can also be specified by @numfields. + if not cnfg.no_convert then + if not numfields then + numfields = {} + local fields = split(line,D.delim) + for i = 1,#fields do + if tonumber(fields[i]) then + append(numfields,i) + end + end + end + if #numfields > 0 then -- there are numerical fields + -- note that using dot as the thousands separator (@thousands_dot) + -- requires a special conversion function! + convert = thousands_dot and try_tonumber or tonumber + end + end + end + -- keep going until finished + while line do + if not line:find ('^%s*$') then + if strip then line = strip(line) end + local fields = split(line,delim) + if convert then + for k = 1,#numfields do + local i = numfields[k] + local val = convert(fields[i]) + if val == nil then + return nil, "not a number: "..fields[i] + else + fields[i] = val + end + end + end + -- [collecting end field] If @last_field_collect then we will collect + -- all extra space-delimited fields into a single last field. + if collect_end and #fields > #D.fieldnames then + local ends,N = {},#D.fieldnames + for i = N+1,#fields do + append(ends,fields[i]) + end + ends = concat(ends,' ') + local cfields = {} + for i = 1,N do cfields[i] = fields[i] end + cfields[N] = cfields[N]..' '..ends + fields = cfields + end + append(D,fields) + end + line = f:read() + end + if opened then f:close() end + if delim == '%s+' then D.delim = ' ' end + if not D.fieldnames then D.fieldnames = {} end + return data.new(D) +end + +local function write_row (data,f,row) + f:write(concat(row,data.delim),'\n') +end + +DataMT.write_row = write_row + +local function write (data,file) + local f,err,opened = open_file(file,'w') + if not f then return nil, err end + if #data.fieldnames > 0 then + f:write(concat(data.fieldnames,data.delim),'\n') + end + for i = 1,#data do + write_row(data,f,data[i]) + end + if opened then f:close() end +end + +DataMT.write = write + +local function massage_fieldnames (fields) + -- [fieldnames must be valid Lua identifiers] fix 0.8 was %A + for i = 1,#fields do + fields[i] = fields[i]:gsub('%W','_') + end +end + + +--- create a new dataset from a table of rows.
    +-- Can specify the fieldnames, else the table must have a field called +-- 'fieldnames', which is either a string of delimiter-separated names, +-- or a table of names.
    +-- If the table does not have a field called 'delim', then an attempt will be +-- made to guess it from the fieldnames string, defaults otherwise to tab. +-- @param d the table. +-- @param fieldnames optional fieldnames +-- @return the table. +function data.new (d,fieldnames) + d.fieldnames = d.fieldnames or fieldnames + if not d.delim and type(d.fieldnames) == 'string' then + d.delim = guess_delim(d.fieldnames) + d.fieldnames = split(d.fieldnames,d.delim) + end + d.fieldnames = make_list(d.fieldnames) + massage_fieldnames(d.fieldnames) + setmetatable(d,DataMT) + -- a query with just the fieldname will return a sequence + -- of values, which seq.copy turns into a table. + return d +end + +local sorted_query = [[ +return function (t) + local i = 0 + local v + local ls = {} + for i,v in ipairs(t) do + if CONDITION then + ls[#ls+1] = v + end + end + table.sort(ls,function(v1,v2) + return SORT_EXPR + end) + local n = #ls + return function() + i = i + 1 + v = ls[i] + if i > n then return end + return FIELDLIST + end +end +]] + +-- question: is this optimized case actually worth the extra code? +local simple_query = [[ +return function (t) + local n = #t + local i = 0 + local v + return function() + repeat + i = i + 1 + v = t[i] + until i > n or CONDITION + if i > n then return end + return FIELDLIST + end +end +]] + +local function is_string (s) + return type(s) == 'string' +end + +local field_error + +local function fieldnames_as_string (data) + return concat(data.fieldnames,',') +end + +local function massage_fields(data,f) + local idx + if f:find '^%d+$' then + idx = tonumber(f) + else + idx = find(data.fieldnames,f) + end + if idx then + return 'v['..idx..']' + else + field_error = f..' not found in '..fieldnames_as_string(data) + return f + end +end + +local List = require 'pl.List' + +local function process_select (data,parms) + --- preparing fields ---- + local res,ret + field_error = nil + local fields = parms.fields + local numfields = fields:find '%$' or #data.fieldnames == 0 + if fields:find '^%s*%*%s*' then + if not numfields then + fields = fieldnames_as_string(data) + else + local ncol = #data[1] + fields = {} + for i = 1,ncol do append(fields,'$'..i) end + fields = concat(fields,',') + end + end + local idpat = patterns.IDEN + if numfields then + idpat = '%$(%d+)' + else + -- massage field names to replace non-identifier chars + fields = rstrip(fields):gsub('[^,%w]','_') + end + local massage_fields = utils.bind1(massage_fields,data) + ret = gsub(fields,idpat,massage_fields) + if field_error then return nil,field_error end + parms.fields = fields + parms.proc_fields = ret + parms.where = parms.where or 'true' + if is_string(parms.where) then + parms.where = gsub(parms.where,idpat,massage_fields) + field_error = nil + end + return true +end + + +parse_select = function(s,data) + local endp + local parms = {} + local w1,w2 = s:find('where ') + local s1,s2 = s:find('sort by ') + if w1 then -- where clause! + endp = (s1 or 0)-1 + parms.where = s:sub(w2+1,endp) + end + if s1 then -- sort by clause (must be last!) + parms.sort_by = s:sub(s2+1) + end + endp = (w1 or s1 or 0)-1 + parms.fields = s:sub(1,endp) + local status,err = process_select(data,parms) + if not status then return nil,err + else return parms end +end + +--- create a query iterator from a select string. +-- Select string has this format:
    +-- FIELDLIST [ where LUA-CONDN [ sort by FIELD] ]
    +-- FIELDLIST is a comma-separated list of valid fields, or '*'.

    +-- The condition can also be a table, with fields 'fields' (comma-sep string or +-- table), 'sort_by' (string) and 'where' (Lua expression string or function) +-- @param data table produced by read +-- @param condn select string or table +-- @param context a list of tables to be searched when resolving functions +-- @param return_row if true, wrap the results in a row table +-- @return an iterator over the specified fields, or nil +-- @return an error message +function data.query(data,condn,context,return_row) + local err + if is_string(condn) then + condn,err = parse_select(condn,data) + if not condn then return nil,err end + elseif type(condn) == 'table' then + if type(condn.fields) == 'table' then + condn.fields = concat(condn.fields,',') + end + if not condn.proc_fields then + local status,err = process_select(data,condn) + if not status then return nil,err end + end + else + return nil, "condition must be a string or a table" + end + local query, k + if condn.sort_by then -- use sorted_query + query = sorted_query + else + query = simple_query + end + local fields = condn.proc_fields or condn.fields + if return_row then + fields = '{'..fields..'}' + end + query,k = query:gsub('FIELDLIST',fields) + if is_string(condn.where) then + query = query:gsub('CONDITION',condn.where) + condn.where = nil + else + query = query:gsub('CONDITION','_condn(v)') + condn.where = function_arg(0,condn.where,'condition.where must be callable') + end + if condn.sort_by then + local expr,sort_var,sort_dir + local sort_by = condn.sort_by + local i1,i2 = sort_by:find('%s+') + if i1 then + sort_var,sort_dir = sort_by:sub(1,i1-1),sort_by:sub(i2+1) + else + sort_var = sort_by + sort_dir = 'asc' + end + if sort_var:match '^%$' then sort_var = sort_var:sub(2) end + sort_var = massage_fields(data,sort_var) + if field_error then return nil,field_error end + if sort_dir == 'asc' then + sort_dir = '<' + else + sort_dir = '>' + end + expr = ('%s %s %s'):format(sort_var:gsub('v','v1'),sort_dir,sort_var:gsub('v','v2')) + query = query:gsub('SORT_EXPR',expr) + end + if condn.where then + query = 'return function(_condn) '..query..' end' + end + if _DEBUG then print(query) end + + local fn,err = loadstring(query,'tmp') + if not fn then return nil,err end + fn = fn() -- get the function + if condn.where then + fn = fn(condn.where) + end + local qfun = fn(data) + if context then + -- [specifying context for condition] @context is a list of tables which are + -- 'injected'into the condition's custom context + append(context,_G) + local lookup = {} + setfenv(qfun,lookup) + setmetatable(lookup,{ + __index = function(tbl,key) + -- _G.print(tbl,key) + for k,t in ipairs(context) do + if t[key] then return t[key] end + end + end + }) + end + return qfun +end + + +DataMT.select = data.query +DataMT.select_row = function(d,condn,context) + return data.query(d,condn,context,true) +end + +--- Filter input using a query. +-- @param Q a query string +-- @param infile filename or file-like object +-- @param outfile filename or file-like object +-- @param dont_fail true if you want to return an error, not just fail +function data.filter (Q,infile,outfile,dont_fail) + local err + local d = data.read(infile or 'stdin') + local out = open_file(outfile or 'stdout') + local iter,err = d:select(Q) + local delim = d.delim + if not iter then + err = 'error: '..err + if dont_fail then + return nil,err + else + utils.quit(1,err) + end + end + while true do + local res = {iter()} + if #res == 0 then break end + out:write(concat(res,delim),'\n') + end +end + +return data + diff --git a/Utils/luarocks/share/lua/5.1/pl/dir.lua b/Utils/luarocks/share/lua/5.1/pl/dir.lua new file mode 100644 index 000000000..5cea49e27 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/dir.lua @@ -0,0 +1,478 @@ +--- Useful functions for getting directory contents and matching them against wildcards. +-- @class module +-- @name pl.dir + +local utils = require 'pl.utils' +local path = require 'pl.path' +local is_windows = path.is_windows +local tablex = require 'pl.tablex' +local ldir = path.dir +local chdir = path.chdir +local mkdir = path.mkdir +local rmdir = path.rmdir +local sub = string.sub +local os,pcall,ipairs,pairs,require,setmetatable,_G = os,pcall,ipairs,pairs,require,setmetatable,_G +local remove = os.remove +local append = table.insert +local wrap = coroutine.wrap +local yield = coroutine.yield +local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise +local List = utils.stdmt.List + +--[[ +module ('pl.dir',utils._module) +]] + +local dir = {} + +local function assert_dir (n,val) + assert_arg(n,val,'string',path.isdir,'not a directory') +end + +local function assert_file (n,val) + assert_arg(n,val,'string',path.isfile,'not a file') +end + +local function filemask(mask) + mask = utils.escape(mask) + return mask:gsub('%%%*','.+'):gsub('%%%?','.')..'$' +end + +--- does the filename match the shell pattern?. +-- (cf. fnmatch.fnmatch in Python, 11.8) +-- @param file A file name +-- @param pattern A shell pattern +-- @return true or false +-- @raise file and pattern must be strings +function dir.fnmatch(file,pattern) + assert_string(1,file) + assert_string(2,pattern) + return path.normcase(file):find(filemask(pattern)) ~= nil +end + +--- return a list of all files which match the pattern. +-- (cf. fnmatch.filter in Python, 11.8) +-- @param files A table containing file names +-- @param pattern A shell pattern. +-- @return list of files +-- @raise file and pattern must be strings +function dir.filter(files,pattern) + assert_arg(1,files,'table') + assert_string(2,pattern) + local res = {} + local mask = filemask(pattern) + for i,f in ipairs(files) do + if f:find(mask) then append(res,f) end + end + return setmetatable(res,List) +end + +local function _listfiles(dir,filemode,match) + local res = {} + local check = utils.choose(filemode,path.isfile,path.isdir) + if not dir then dir = '.' end + for f in ldir(dir) do + if f ~= '.' and f ~= '..' then + local p = path.join(dir,f) + if check(p) and (not match or match(p)) then + append(res,p) + end + end + end + return setmetatable(res,List) +end + +--- return a list of all files in a directory which match the a shell pattern. +-- @param dir A directory. If not given, all files in current directory are returned. +-- @param mask A shell pattern. If not given, all files are returned. +-- @return lsit of files +-- @raise dir and mask must be strings +function dir.getfiles(dir,mask) + assert_dir(1,dir) + assert_string(2,mask) + local match + if mask then + mask = filemask(mask) + match = function(f) + return f:find(mask) + end + end + return _listfiles(dir,true,match) +end + +--- return a list of all subdirectories of the directory. +-- @param dir A directory +-- @return a list of directories +-- @raise dir must be a string +function dir.getdirectories(dir) + assert_dir(1,dir) + return _listfiles(dir,false) +end + +local function quote_argument (f) + f = path.normcase(f) + if f:find '%s' then + return '"'..f..'"' + else + return f + end +end + + +local alien,ffi,ffi_checked,CopyFile,MoveFile,GetLastError,win32_errors,cmd_tmpfile + +local function execute_command(cmd,parms) + if not cmd_tmpfile then cmd_tmpfile = path.tmpname () end + local err = path.is_windows and ' > ' or ' 2> ' + cmd = cmd..' '..parms..err..cmd_tmpfile + local ret = utils.execute(cmd) + if not ret then + return false,(utils.readfile(cmd_tmpfile):gsub('\n(.*)','')) + else + return true + end +end + +local function find_ffi_copyfile () + if not ffi_checked then + ffi_checked = true + local res + res,alien = pcall(require,'alien') + if not res then + alien = nil + res, ffi = pcall(require,'ffi') + end + if not res then + ffi = nil + return + end + else + return + end + if alien then + -- register the Win32 CopyFile and MoveFile functions + local kernel = alien.load('kernel32.dll') + CopyFile = kernel.CopyFileA + CopyFile:types{'string','string','int',ret='int',abi='stdcall'} + MoveFile = kernel.MoveFileA + MoveFile:types{'string','string',ret='int',abi='stdcall'} + GetLastError = kernel.GetLastError + GetLastError:types{ret ='int', abi='stdcall'} + elseif ffi then + ffi.cdef [[ + int CopyFileA(const char *src, const char *dest, int iovr); + int MoveFileA(const char *src, const char *dest); + int GetLastError(); + ]] + CopyFile = ffi.C.CopyFileA + MoveFile = ffi.C.MoveFileA + GetLastError = ffi.C.GetLastError + end + win32_errors = { + ERROR_FILE_NOT_FOUND = 2, + ERROR_PATH_NOT_FOUND = 3, + ERROR_ACCESS_DENIED = 5, + ERROR_WRITE_PROTECT = 19, + ERROR_BAD_UNIT = 20, + ERROR_NOT_READY = 21, + ERROR_WRITE_FAULT = 29, + ERROR_READ_FAULT = 30, + ERROR_SHARING_VIOLATION = 32, + ERROR_LOCK_VIOLATION = 33, + ERROR_HANDLE_DISK_FULL = 39, + ERROR_BAD_NETPATH = 53, + ERROR_NETWORK_BUSY = 54, + ERROR_DEV_NOT_EXIST = 55, + ERROR_FILE_EXISTS = 80, + ERROR_OPEN_FAILED = 110, + ERROR_INVALID_NAME = 123, + ERROR_BAD_PATHNAME = 161, + ERROR_ALREADY_EXISTS = 183, + } +end + +local function two_arguments (f1,f2) + return quote_argument(f1)..' '..quote_argument(f2) +end + +local function file_op (is_copy,src,dest,flag) + if flag == 1 and path.exists(dest) then + return false,"cannot overwrite destination" + end + if is_windows then + -- if we haven't tried to load Alien/LuaJIT FFI before, then do so + find_ffi_copyfile() + -- fallback if there's no Alien, just use DOS commands *shudder* + -- 'rename' involves a copy and then deleting the source. + if not CopyFile then + src = path.normcase(src) + dest = path.normcase(dest) + local cmd = is_copy and 'copy' or 'rename' + local res, err = execute_command('copy',two_arguments(src,dest)) + if not res then return nil,err end + if not is_copy then + return execute_command('del',quote_argument(src)) + end + else + if path.isdir(dest) then + dest = path.join(dest,path.basename(src)) + end + local ret + if is_copy then ret = CopyFile(src,dest,flag) + else ret = MoveFile(src,dest) end + if ret == 0 then + local err = GetLastError() + for name,value in pairs(win32_errors) do + if value == err then return false,name end + end + return false,"Error #"..err + else return true + end + end + else -- for Unix, just use cp for now + return execute_command(is_copy and 'cp' or 'mv', + two_arguments(src,dest)) + end +end + +--- copy a file. +-- @param src source file +-- @param dest destination file or directory +-- @param flag true if you want to force the copy (default) +-- @return true if operation succeeded +-- @raise src and dest must be strings +function dir.copyfile (src,dest,flag) + assert_string(1,src) + assert_string(2,dest) + flag = flag==nil or flag + return file_op(true,src,dest,flag and 0 or 1) +end + +--- move a file. +-- @param src source file +-- @param dest destination file or directory +-- @return true if operation succeeded +-- @raise src and dest must be strings +function dir.movefile (src,dest) + assert_string(1,src) + assert_string(2,dest) + return file_op(false,src,dest,0) +end + +local function _dirfiles(dir,attrib) + local dirs = {} + local files = {} + for f in ldir(dir) do + if f ~= '.' and f ~= '..' then + local p = path.join(dir,f) + local mode = attrib(p,'mode') + if mode=='directory' then + append(dirs,f) + else + append(files,f) + end + end + end + return setmetatable(dirs,List),setmetatable(files,List) +end + + +local function _walker(root,bottom_up,attrib) + local dirs,files = _dirfiles(root,attrib) + if not bottom_up then yield(root,dirs,files) end + for i,d in ipairs(dirs) do + _walker(root..path.sep..d,bottom_up,attrib) + end + if bottom_up then yield(root,dirs,files) end +end + +--- return an iterator which walks through a directory tree starting at root. +-- The iterator returns (root,dirs,files) +-- Note that dirs and files are lists of names (i.e. you must say path.join(root,d) +-- to get the actual full path) +-- If bottom_up is false (or not present), then the entries at the current level are returned +-- before we go deeper. This means that you can modify the returned list of directories before +-- continuing. +-- This is a clone of os.walk from the Python libraries. +-- @param root A starting directory +-- @param bottom_up False if we start listing entries immediately. +-- @param follow_links follow symbolic links +-- @return an iterator returning root,dirs,files +-- @raise root must be a string +function dir.walk(root,bottom_up,follow_links) + assert_string(1,root) + if not path.isdir(root) then return raise 'not a directory' end + local attrib + if path.is_windows or not follow_links then + attrib = path.attrib + else + attrib = path.link_attrib + end + return wrap(function () _walker(root,bottom_up,attrib) end) +end + +--- remove a whole directory tree. +-- @param fullpath A directory path +-- @return true or nil +-- @return error if failed +-- @raise fullpath must be a string +function dir.rmtree(fullpath) + assert_string(1,fullpath) + if not path.isdir(fullpath) then return raise 'not a directory' end + if path.islink(fullpath) then return false,'will not follow symlink' end + for root,dirs,files in dir.walk(fullpath,true) do + for i,f in ipairs(files) do + remove(path.join(root,f)) + end + rmdir(root) + end + return true +end + +local dirpat +if path.is_windows then + dirpat = '(.+)\\[^\\]+$' +else + dirpat = '(.+)/[^/]+$' +end + +local _makepath +function _makepath(p) + -- windows root drive case + if p:find '^%a:[\\]*$' then + return true + end + if not path.isdir(p) then + local subp = p:match(dirpat) + if not _makepath(subp) then return raise ('cannot create '..subp) end + return mkdir(p) + else + return true + end +end + +--- create a directory path. +-- This will create subdirectories as necessary! +-- @param p A directory path +-- @return a valid created path +-- @raise p must be a string +function dir.makepath (p) + assert_string(1,p) + return _makepath(path.normcase(path.abspath(p))) +end + + +--- clone a directory tree. Will always try to create a new directory structure +-- if necessary. +-- @param path1 the base path of the source tree +-- @param path2 the new base path for the destination +-- @param file_fun an optional function to apply on all files +-- @param verbose an optional boolean to control the verbosity of the output. +-- It can also be a logging function that behaves like print() +-- @return true, or nil +-- @return error message, or list of failed directory creations +-- @return list of failed file operations +-- @raise path1 and path2 must be strings +-- @usage clonetree('.','../backup',copyfile) +function dir.clonetree (path1,path2,file_fun,verbose) + assert_string(1,path1) + assert_string(2,path2) + if verbose == true then verbose = print end + local abspath,normcase,isdir,join = path.abspath,path.normcase,path.isdir,path.join + local faildirs,failfiles = {},{} + if not isdir(path1) then return raise 'source is not a valid directory' end + path1 = abspath(normcase(path1)) + path2 = abspath(normcase(path2)) + if verbose then verbose('normalized:',path1,path2) end + -- particularly NB that the new path isn't fully contained in the old path + if path1 == path2 then return raise "paths are the same" end + local i1,i2 = path2:find(path1,1,true) + if i2 == #path1 and path2:sub(i2+1,i2+1) == path.sep then + return raise 'destination is a subdirectory of the source' + end + local cp = path.common_prefix (path1,path2) + local idx = #cp + if idx == 0 then -- no common path, but watch out for Windows paths! + if path1:sub(2,2) == ':' then idx = 3 end + end + for root,dirs,files in dir.walk(path1) do + local opath = path2..root:sub(idx) + if verbose then verbose('paths:',opath,root) end + if not isdir(opath) then + local ret = dir.makepath(opath) + if not ret then append(faildirs,opath) end + if verbose then verbose('creating:',opath,ret) end + end + if file_fun then + for i,f in ipairs(files) do + local p1 = join(root,f) + local p2 = join(opath,f) + local ret = file_fun(p1,p2) + if not ret then append(failfiles,p2) end + if verbose then + verbose('files:',p1,p2,ret) + end + end + end + end + return true,faildirs,failfiles +end + +--- return an iterator over all entries in a directory tree +-- @param d a directory +-- @return an iterator giving pathname and mode (true for dir, false otherwise) +-- @raise d must be a non-empty string +function dir.dirtree( d ) + assert( d and d ~= "", "directory parameter is missing or empty" ) + local exists, isdir = path.exists, path.isdir + local sep = path.sep + + local last = sub ( d, -1 ) + if last == sep or last == '/' then + d = sub( d, 1, -2 ) + end + + local function yieldtree( dir ) + for entry in ldir( dir ) do + if entry ~= "." and entry ~= ".." then + entry = dir .. sep .. entry + if exists(entry) then -- Just in case a symlink is broken. + local is_dir = isdir(entry) + yield( entry, is_dir ) + if is_dir then + yieldtree( entry ) + end + end + end + end + end + + return wrap( function() yieldtree( d ) end ) +end + + +--- Recursively returns all the file starting at path. It can optionally take a shell pattern and +-- only returns files that match pattern. If a pattern is given it will do a case insensitive search. +-- @param start_path {string} A directory. If not given, all files in current directory are returned. +-- @param pattern {string} A shell pattern. If not given, all files are returned. +-- @return Table containing all the files found recursively starting at path and filtered by pattern. +-- @raise start_path must be a string +function dir.getallfiles( start_path, pattern ) + assert( type( start_path ) == "string", "bad argument #1 to 'GetAllFiles' (Expected string but recieved " .. type( start_path ) .. ")" ) + pattern = pattern or "" + + local files = {} + local normcase = path.normcase + for filename, mode in dir.dirtree( start_path ) do + if not mode then + local mask = filemask( pattern ) + if normcase(filename):find( mask ) then + files[#files + 1] = filename + end + end + end + + return files +end + +return dir diff --git a/Utils/luarocks/share/lua/5.1/pl/file.lua b/Utils/luarocks/share/lua/5.1/pl/file.lua new file mode 100644 index 000000000..6c82e2859 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/file.lua @@ -0,0 +1,69 @@ +--- File manipulation functions: reading, writing, moving and copying. +-- @class module +-- @name pl.file +local os = os +local utils = require 'pl.utils' +local dir = require 'pl.dir' +local path = require 'pl.path' + +--[[ +module ('pl.file',utils._module) +]] +local file = {} + +--- return the contents of a file as a string +-- @class function +-- @name file.read +-- @param filename The file path +-- @return file contents +file.read = utils.readfile + +--- write a string to a file +-- @class function +-- @name file.write +-- @param filename The file path +-- @param str The string +file.write = utils.writefile + +--- copy a file. +-- @class function +-- @name file.copy +-- @param src source file +-- @param dest destination file +-- @param flag true if you want to force the copy (default) +-- @return true if operation succeeded +file.copy = dir.copyfile + +--- move a file. +-- @class function +-- @name file.move +-- @param src source file +-- @param dest destination file +-- @return true if operation succeeded, else false and the reason for the error. +file.move = dir.movefile + +--- Return the time of last access as the number of seconds since the epoch. +-- @class function +-- @name file.access_time +-- @param path A file path +file.access_time = path.getatime + +---Return when the file was created. +-- @class function +-- @name file.creation_time +-- @param path A file path +file.creation_time = path.getctime + +--- Return the time of last modification +-- @class function +-- @name file.modified_time +-- @param path A file path +file.modified_time = path.getmtime + +--- Delete a file +-- @class function +-- @name file.delete +-- @param path A file path +file.delete = os.remove + +return file diff --git a/Utils/luarocks/share/lua/5.1/pl/func.lua b/Utils/luarocks/share/lua/5.1/pl/func.lua new file mode 100644 index 000000000..3711c9a71 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/func.lua @@ -0,0 +1,379 @@ +--- Functional helpers like composition, binding and placeholder expressions. +-- Placeholder expressions are useful for short anonymous functions, and were +-- inspired by the Boost Lambda library. +--

    +-- utils.import 'pl.func'
    +-- ls = List{10,20,30}
    +-- = ls:map(_1+1)
    +--    {11,21,31}
    +-- 
    +-- They can also be used to bind particular arguments of a function. +--
    +-- p = bind(print,'start>',_0)
    +-- p(10,20,30)
    +-- start>   10   20  30
    +-- 
    +-- See the Guide +-- @class module +-- @name pl.func +local type,select,setmetatable,getmetatable,rawset = type,select,setmetatable,getmetatable,rawset +local concat,append = table.concat,table.insert +local max = math.max +local print,tostring = print,tostring +local pairs,ipairs,loadstring,rawget,unpack = pairs,ipairs,loadstring,rawget,unpack +local _G = _G +local utils = require 'pl.utils' +local tablex = require 'pl.tablex' +local map = tablex.map +local _DEBUG = rawget(_G,'_DEBUG') +local assert_arg = utils.assert_arg + +--[[ +module ('pl.func',utils._module) +]] + +local func = {} + +-- metatable for Placeholder Expressions (PE) +local _PEMT = {} + +local function P (t) + setmetatable(t,_PEMT) + return t +end + +func.PE = P + +local function isPE (obj) + return getmetatable(obj) == _PEMT +end + +func.isPE = isPE + +-- construct a placeholder variable (e.g _1 and _2) +local function PH (idx) + return P {op='X',repr='_'..idx, index=idx} +end + +-- construct a constant placeholder variable (e.g _C1 and _C2) +local function CPH (idx) + return P {op='X',repr='_C'..idx, index=idx} +end + +func._1,func._2,func._3,func._4,func._5 = PH(1),PH(2),PH(3),PH(4),PH(5) +func._0 = P{op='X',repr='...',index=0} + +function func.Var (name) + local ls = utils.split(name,'[%s,]+') + local res = {} + for _,n in ipairs(ls) do + append(res,P{op='X',repr=n,index=0}) + end + return unpack(res) +end + +function func._ (value) + return P{op='X',repr=value,index='wrap'} +end + +local repr + +func.Nil = func.Var 'nil' + +function _PEMT.__index(obj,key) + return P{op='[]',obj,key} +end + +function _PEMT.__call(fun,...) + return P{op='()',fun,...} +end + +function _PEMT.__tostring (e) + return repr(e) +end + +function _PEMT.__unm(arg) + return P{op='-',arg} +end + +function func.Not (arg) + return P{op='not',arg} +end + +function func.Len (arg) + return P{op='#',arg} +end + + +local function binreg(context,t) + for name,op in pairs(t) do + rawset(context,name,function(x,y) + return P{op=op,x,y} + end) + end +end + +local function import_name (name,fun,context) + rawset(context,name,function(...) + return P{op='()',fun,...} + end) +end + +local imported_functions = {} + +local function is_global_table (n) + return type(_G[n]) == 'table' +end + +--- wrap a table of functions. This makes them available for use in +-- placeholder expressions. +-- @param tname a table name +-- @param context context to put results, defaults to environment of caller +function func.import(tname,context) + assert_arg(1,tname,'string',is_global_table,'arg# 1: not a name of a global table') + local t = _G[tname] + context = context or _G + for name,fun in pairs(t) do + import_name(name,fun,context) + imported_functions[fun] = name + end +end + +--- register a function for use in placeholder expressions. +-- @param fun a function +-- @param name an optional name +-- @return a placeholder functiond +function func.register (fun,name) + assert_arg(1,fun,'function') + if name then + assert_arg(2,name,'string') + imported_functions[fun] = name + end + return function(...) + return P{op='()',fun,...} + end +end + +function func.lookup_imported_name (fun) + return imported_functions[fun] +end + +local function _arg(...) return ... end + +function func.Args (...) + return P{op='()',_arg,...} +end + +-- binary and unary operators, with their precedences (see 2.5.6) +local operators = { + ['or'] = 0, + ['and'] = 1, + ['=='] = 2, ['~='] = 2, ['<'] = 2, ['>'] = 2, ['<='] = 2, ['>='] = 2, + ['..'] = 3, + ['+'] = 4, ['-'] = 4, + ['*'] = 5, ['/'] = 5, ['%'] = 5, + ['not'] = 6, ['#'] = 6, ['-'] = 6, + ['^'] = 7 +} + +-- comparisons (as prefix functions) +binreg (func,{And='and',Or='or',Eq='==',Lt='<',Gt='>',Le='<=',Ge='>='}) + +-- standard binary operators (as metamethods) +binreg (_PEMT,{__add='+',__sub='-',__mul='*',__div='/',__mod='%',__pow='^',__concat='..'}) + +binreg (_PEMT,{__eq='=='}) + +--- all elements of a table except the first. +-- @param ls a list-like table. +function func.tail (ls) + assert_arg(1,ls,'table') + local res = {} + for i = 2,#ls do + append(res,ls[i]) + end + return res +end + +--- create a string representation of a placeholder expression. +-- @param e a placeholder expression +-- @param lastpred not used +function repr (e,lastpred) + local tail = func.tail + if isPE(e) then + local pred = operators[e.op] + local ls = map(repr,e,pred) + if pred then --unary or binary operator + if #ls ~= 1 then + local s = concat(ls,' '..e.op..' ') + if lastpred and lastpred > pred then + s = '('..s..')' + end + return s + else + return e.op..' '..ls[1] + end + else -- either postfix, or a placeholder + if e.op == '[]' then + return ls[1]..'['..ls[2]..']' + elseif e.op == '()' then + local fn + if ls[1] ~= nil then -- was _args, undeclared! + fn = ls[1] + else + fn = '' + end + return fn..'('..concat(tail(ls),',')..')' + else + return e.repr + end + end + elseif type(e) == 'string' then + return '"'..e..'"' + elseif type(e) == 'function' then + local name = func.lookup_imported_name(e) + if name then return name else return tostring(e) end + else + return tostring(e) --should not really get here! + end +end +func.repr = repr + +-- collect all the non-PE values in this PE into vlist, and replace each occurence +-- with a constant PH (_C1, etc). Return the maximum placeholder index found. +local collect_values +function collect_values (e,vlist) + if isPE(e) then + if e.op ~= 'X' then + local m = 0 + for i,subx in ipairs(e) do + local pe = isPE(subx) + if pe then + if subx.op == 'X' and subx.index == 'wrap' then + subx = subx.repr + pe = false + else + m = max(m,collect_values(subx,vlist)) + end + end + if not pe then + append(vlist,subx) + e[i] = CPH(#vlist) + end + end + return m + else -- was a placeholder, it has an index... + return e.index + end + else -- plain value has no placeholder dependence + return 0 + end +end +func.collect_values = collect_values + +--- instantiate a PE into an actual function. First we find the largest placeholder used, +-- e.g. _2; from this a list of the formal parameters can be build. Then we collect and replace +-- any non-PE values from the PE, and build up a constant binding list. +-- Finally, the expression can be compiled, and e.__PE_function is set. +-- @param e a placeholder expression +-- @return a function +function func.instantiate (e) + local consts,values,parms = {},{},{} + local rep, err, fun + local n = func.collect_values(e,values) + for i = 1,#values do + append(consts,'_C'..i) + if _DEBUG then print(i,values[i]) end + end + for i =1,n do + append(parms,'_'..i) + end + consts = concat(consts,',') + parms = concat(parms,',') + rep = repr(e) + local fstr = ('return function(%s) return function(%s) return %s end end'):format(consts,parms,rep) + if _DEBUG then print(fstr) end + fun,err = loadstring(fstr,'fun') + if not fun then return nil,err end + fun = fun() -- get wrapper + fun = fun(unpack(values)) -- call wrapper (values could be empty) + e.__PE_function = fun + return fun +end + +--- instantiate a PE unless it has already been done. +-- @param e a placeholder expression +-- @return the function +function func.I(e) + if rawget(e,'__PE_function') then + return e.__PE_function + else return func.instantiate(e) + end +end + +utils.add_function_factory(_PEMT,func.I) + +--- bind the first parameter of the function to a value. +-- @class function +-- @name func.curry +-- @param fn a function of one or more arguments +-- @param p a value +-- @return a function of one less argument +-- @usage (curry(math.max,10))(20) == math.max(10,20) +func.curry = utils.bind1 + +--- create a function which chains two functions. +-- @param f a function of at least one argument +-- @param g a function of at least one argument +-- @return a function +-- @usage printf = compose(io.write,string.format) +function func.compose (f,g) + return function(...) return f(g(...)) end +end + +--- bind the arguments of a function to given values. +-- bind(fn,v,_2) is equivalent to curry(fn,v). +-- @param fn a function of at least one argument +-- @param ... values or placeholder variables +-- @return a function +-- @usage (bind(f,_1,a))(b) == f(a,b) +-- @usage (bind(f,_2,_1))(a,b) == f(b,a) +function func.bind(fn,...) + local args = table.pack(...) + local holders,parms,bvalues,values = {},{},{'fn'},{} + local nv,maxplace,varargs = 1,0,false + for i = 1,args.n do + local a = args[i] + if isPE(a) and a.op == 'X' then + append(holders,a.repr) + maxplace = max(maxplace,a.index) + if a.index == 0 then varargs = true end + else + local v = '_v'..nv + append(bvalues,v) + append(holders,v) + append(values,a) + nv = nv + 1 + end + end + for np = 1,maxplace do + append(parms,'_'..np) + end + if varargs then append(parms,'...') end + bvalues = concat(bvalues,',') + parms = concat(parms,',') + holders = concat(holders,',') + local fstr = ([[ +return function (%s) + return function(%s) return fn(%s) end +end +]]):format(bvalues,parms,holders) + if _DEBUG then print(fstr) end + local res,err = loadstring(fstr) + res = res() + return res(fn,unpack(values)) +end + +return func + + diff --git a/Utils/luarocks/share/lua/5.1/pl/init.lua b/Utils/luarocks/share/lua/5.1/pl/init.lua new file mode 100644 index 000000000..ee336e57c --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/init.lua @@ -0,0 +1,47 @@ +-------------- +-- entry point for loading all PL libraries only on demand. +-- Requiring 'pl' means that whenever a module is accesssed (e.g. utils.split) +-- then that module is dynamically loaded. The submodules are all brought into +-- the global space. +-- @class module +-- @name pl + +local modules = { + utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true, + input=true,seq=true,lexer=true,stringx=true, + config=true,pretty=true,data=true,func=true,text=true, + operator=true,lapp=true,array2d=true, + comprehension=true,xml=true, + test = true, app = true, file = true, class = true, List = true, + Map = true, Set = true, OrderedMap = true, MultiMap = true, + Date = true, + -- classes -- +} +_G.utils = require 'pl.utils' + +for name,klass in pairs(_G.utils.stdmt) do + klass.__index = function(t,key) + return require ('pl.'..name)[key] + end; +end + +local _hook +setmetatable(_G,{ + hook = function(handler) + _hook = handler + end, + __index = function(t,name) + local found = modules[name] + -- either true, or the name of the module containing this class. + -- either way, we load the required module and make it globally available. + if found then + -- e..g pretty.dump causes pl.pretty to become available as 'pretty' + rawset(_G,name,require('pl.'..name)) + return _G[name] + elseif _hook then + return _hook(t,name) + end + end +}) + +if _G.PENLIGHT_STRICT then require 'pl.strict' end diff --git a/Utils/luarocks/share/lua/5.1/pl/input.lua b/Utils/luarocks/share/lua/5.1/pl/input.lua new file mode 100644 index 000000000..ea566f470 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/input.lua @@ -0,0 +1,172 @@ +--- Iterators for extracting words or numbers from an input source. +--
    +--    require 'pl'
    +--    local total,n = seq.sum(input.numbers())
    +--    print('average',total/n)
    +-- 
    +--

    See here +-- @class module +-- @name pl.input +local strfind = string.find +local strsub = string.sub +local strmatch = string.match +local pairs,type,unpack,tonumber = pairs,type,unpack,tonumber +local utils = require 'pl.utils' +local patterns = utils.patterns +local io = io +local assert_arg = utils.assert_arg + +--[[ +module ('pl.input',utils._module) +]] + +local input = {} + +--- create an iterator over all tokens. +-- based on allwords from PiL, 7.1 +-- @param getter any function that returns a line of text +-- @param pattern +-- @param fn Optionally can pass a function to process each token as it/s found. +-- @return an iterator +function input.alltokens (getter,pattern,fn) + local line = getter() -- current line + local pos = 1 -- current position in the line + return function () -- iterator function + while line do -- repeat while there are lines + local s, e = strfind(line, pattern, pos) + if s then -- found a word? + pos = e + 1 -- next position is after this token + local res = strsub(line, s, e) -- return the token + if fn then res = fn(res) end + return res + else + line = getter() -- token not found; try next line + pos = 1 -- restart from first position + end + end + return nil -- no more lines: end of traversal + end +end +local alltokens = input.alltokens + +-- question: shd this _split_ a string containing line feeds? + +--- create a function which grabs the next value from a source. If the source is a string, then the getter +-- will return the string and thereafter return nil. If not specified then the source is assumed to be stdin. +-- @param f a string or a file-like object (i.e. has a read() method which returns the next line) +-- @return a getter function +function input.create_getter(f) + if f then + if type(f) == 'string' then + local ls = utils.split(f,'\n') + local i,n = 0,#ls + return function() + i = i + 1 + if i > n then return nil end + return ls[i] + end + else + -- anything that supports the read() method! + if not f.read then error('not a file-like object') end + return function() return f:read() end + end + else + return io.read -- i.e. just read from stdin + end +end + +--- generate a sequence of numbers from a source. +-- @param f A source +-- @return An iterator +function input.numbers(f) + return alltokens(input.create_getter(f), + '('..patterns.FLOAT..')',tonumber) +end + +--- generate a sequence of words from a source. +-- @param f A source +-- @return An iterator +function input.words(f) + return alltokens(input.create_getter(f),"%w+") +end + +local function apply_tonumber (no_fail,...) + local args = {...} + for i = 1,#args do + local n = tonumber(args[i]) + if n == nil then + if not no_fail then return nil,args[i] end + else + args[i] = n + end + end + return args +end + +--- parse an input source into fields. +-- By default, will fail if it cannot convert a field to a number. +-- @param ids a list of field indices, or a maximum field index +-- @param delim delimiter to parse fields (default space) +-- @param f a source @see create_getter +-- @param opts option table, {no_fail=true} +-- @return an iterator with the field values +-- @usage for x,y in fields {2,3} do print(x,y) end -- 2nd and 3rd fields from stdin +function input.fields (ids,delim,f,opts) + local sep + local s + local getter = input.create_getter(f) + local no_fail = opts and opts.no_fail + local no_convert = opts and opts.no_convert + if not delim or delim == ' ' then + delim = '%s' + sep = '%s+' + s = '%s*' + else + sep = delim + s = '' + end + local max_id = 0 + if type(ids) == 'table' then + for i,id in pairs(ids) do + if id > max_id then max_id = id end + end + else + max_id = ids + ids = {} + for i = 1,max_id do ids[#ids+1] = i end + end + local pat = '[^'..delim..']*' + local k = 1 + for i = 1,max_id do + if ids[k] == i then + k = k + 1 + s = s..'('..pat..')' + else + s = s..pat + end + if i < max_id then + s = s..sep + end + end + local linecount = 1 + return function() + local line,results,err + repeat + line = getter() + linecount = linecount + 1 + if not line then return nil end + if no_convert then + results = {strmatch(line,s)} + else + results,err = apply_tonumber(no_fail,strmatch(line,s)) + if not results then + utils.quit("line "..(linecount-1)..": cannot convert '"..err.."' to number") + end + end + until #results > 0 + return unpack(results) + end +end + +return input + diff --git a/Utils/luarocks/share/lua/5.1/pl/lapp.lua b/Utils/luarocks/share/lua/5.1/pl/lapp.lua new file mode 100644 index 000000000..80a81294a --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/lapp.lua @@ -0,0 +1,350 @@ +--- Simple command-line parsing using human-readable specification. +-- Supports GNU-style parameters. +--

    +--      lapp = require 'pl.lapp'
    +--      local args = lapp [[
    +--      Does some calculations
    +--        -o,--offset (default 0.0)  Offset to add to scaled number
    +--        -s,--scale  (number)  Scaling factor
    +--         <number> (number )  Number to be scaled
    +--      ]]
    +--
    +--      print(args.offset + args.scale * args.number)
    +-- 
    +-- Lines begining with '-' are flags; there may be a short and a long name; +-- lines begining wih '<var>' are arguments. Anything in parens after +-- the flag/argument is either a default, a type name or a range constraint. +--

    See the Guide +-- @class module +-- @name pl.lapp + +local status,sip = pcall(require,'pl.sip') +if not status then + sip = require 'sip' +end +local match = sip.match_at_start +local append,tinsert = table.insert,table.insert + +--[[ +module('pl.lapp') +]] + +local function lines(s) return s:gmatch('([^\n]*)\n') end +local function lstrip(str) return str:gsub('^%s+','') end +local function strip(str) return lstrip(str):gsub('%s+$','') end +local function at(s,k) return s:sub(k,k) end +local function isdigit(s) return s:find('^%d+$') == 1 end + +local lapp = {} + +local open_files,parms,aliases,parmlist,usage,windows,script + +lapp.callback = false -- keep Strict happy + +local filetypes = { + stdin = {io.stdin,'file-in'}, stdout = {io.stdout,'file-out'}, + stderr = {io.stderr,'file-out'} +} + +--- controls whether to dump usage on error. +-- Defaults to true +lapp.show_usage_error = true + +--- quit this script immediately. +-- @param msg optional message +-- @param no_usage suppress 'usage' display +function lapp.quit(msg,no_usage) + if msg then + io.stderr:write(msg..'\n\n') + end + if not no_usage then + io.stderr:write(usage) + end + os.exit(1); +end + +--- print an error to stderr and quit. +-- @param msg a message +-- @param no_usage suppress 'usage' display +function lapp.error(msg,no_usage) + if not lapp.show_usage_error then + no_usage = true + end + lapp.quit(script..':'..msg,no_usage) +end + +--- open a file. +-- This will quit on error, and keep a list of file objects for later cleanup. +-- @param file filename +-- @param opt same as second parameter of io.open +function lapp.open (file,opt) + local val,err = io.open(file,opt) + if not val then lapp.error(err,true) end + append(open_files,val) + return val +end + +--- quit if the condition is false. +-- @param condn a condition +-- @param msg an optional message +function lapp.assert(condn,msg) + if not condn then + lapp.error(msg) + end +end + +local function range_check(x,min,max,parm) + lapp.assert(min <= x and max >= x,parm..' out of range') +end + +local function xtonumber(s) + local val = tonumber(s) + if not val then lapp.error("unable to convert to number: "..s) end + return val +end + +local function is_filetype(type) + return type == 'file-in' or type == 'file-out' +end + +local types + +local function convert_parameter(ps,val) + if ps.converter then + val = ps.converter(val) + end + if ps.type == 'number' then + val = xtonumber(val) + elseif is_filetype(ps.type) then + val = lapp.open(val,(ps.type == 'file-in' and 'r') or 'w' ) + elseif ps.type == 'boolean' then + val = true + end + if ps.constraint then + ps.constraint(val) + end + return val +end + +--- add a new type to Lapp. These appear in parens after the value like +-- a range constraint, e.g. ' (integer) Process PID' +-- @param name name of type +-- @param converter either a function to convert values, or a Lua type name. +-- @param constraint optional function to verify values, should use lapp.error +-- if failed. +function lapp.add_type (name,converter,constraint) + types[name] = {converter=converter,constraint=constraint} +end + +local function force_short(short) + lapp.assert(#short==1,short..": short parameters should be one character") +end + +local function process_default (sval) + local val = tonumber(sval) + if val then -- we have a number! + return val,'number' + elseif filetypes[sval] then + local ft = filetypes[sval] + return ft[1],ft[2] + else + if sval:match '^["\']' then sval = sval:sub(2,-2) end + return sval,'string' + end +end + +--- process a Lapp options string. +-- Usually called as lapp(). +-- @param str the options text +-- @return a table with parameter-value pairs +function lapp.process_options_string(str) + local results = {} + local opts = {at_start=true} + local varargs + open_files = {} + parms = {} + aliases = {} + parmlist = {} + types = {} + + local function check_varargs(s) + local res,cnt = s:gsub('^%.%.%.%s*','') + return res, (cnt > 0) + end + + local function set_result(ps,parm,val) + if not ps.varargs then + results[parm] = val + else + if not results[parm] then + results[parm] = { val } + else + append(results[parm],val) + end + end + end + + usage = str + + for line in lines(str) do + local res = {} + local optspec,optparm,i1,i2,defval,vtype,constraint,rest + line = lstrip(line) + local function check(str) + return match(str,line,res) + end + + -- flags: either '-', '-,--' or '--' + if check '-$v{short}, --$v{long} $' or check '-$v{short} $' or check '--$v{long} $' then + if res.long then + optparm = res.long + if res.short then aliases[res.short] = optparm end + else + optparm = res.short + end + if res.short then force_short(res.short) end + res.rest, varargs = check_varargs(res.rest) + elseif check '$<{name} $' then -- is it ? + -- so becomes input_file ... + optparm,rest = res.name:match '([^%.]+)(.*)' + optparm = optparm:gsub('%A','_') + varargs = rest == '...' + append(parmlist,optparm) + end + if res.rest then -- this is not a pure doc line + line = res.rest + res = {} + -- do we have (default ) or ()? + if match('$({def} $',line,res) or match('$({def}',line,res) then + local typespec = strip(res.def) + if match('default $',typespec,res) then + defval,vtype = process_default(res[1]) + elseif match('$f{min}..$f{max}',typespec,res) then + local min,max = res.min,res.max + vtype = 'number' + constraint = function(x) + range_check(x,min,max,optparm) + end + else -- () just contains type of required parameter + vtype = typespec + end + else -- must be a plain flag, no extra parameter required + defval = false + vtype = 'boolean' + end + local ps = { + type = vtype, + defval = defval, + required = defval == nil, + comment = res.rest or optparm, + constraint = constraint, + varargs = varargs + } + varargs = nil + if types[vtype] then + local converter = types[vtype].converter + if type(converter) == 'string' then + ps.type = converter + else + ps.converter = converter + end + ps.constraint = types[vtype].constraint + end + parms[optparm] = ps + end + end + -- cool, we have our parms, let's parse the command line args + local iparm = 1 + local iextra = 1 + local i = 1 + local parm,ps,val + + while i <= #arg do + local theArg = arg[i] + local res = {} + -- look for a flag, - or -- + if match('--$v{long}',theArg,res) or match('-$v{short}',theArg,res) then + if res.long then -- long option + parm = res.long + elseif #res.short == 1 then + parm = res.short + else + local parmstr = res.short + parm = at(parmstr,1) + if isdigit(at(parmstr,2)) then + -- a short option followed by a digit is an exception (for AW;)) + -- push ahead into the arg array + tinsert(arg,i+1,parmstr:sub(2)) + else + -- push multiple flags into the arg array! + for k = 2,#parmstr do + tinsert(arg,i+k-1,'-'..at(parmstr,k)) + end + end + end + if parm == 'h' or parm == 'help' then + lapp.quit() + end + if aliases[parm] then parm = aliases[parm] end + else -- a parameter + parm = parmlist[iparm] + if not parm then + -- extra unnamed parameters are indexed starting at 1 + parm = iextra + ps = { type = 'string' } + parms[parm] = ps + iextra = iextra + 1 + else + ps = parms[parm] + end + if not ps.varargs then + iparm = iparm + 1 + end + val = theArg + end + ps = parms[parm] + if not ps then lapp.error("unrecognized parameter: "..parm) end + if ps.type ~= 'boolean' then -- we need a value! This should follow + if not val then + i = i + 1 + val = arg[i] + end + lapp.assert(val,parm.." was expecting a value") + end + ps.used = true + val = convert_parameter(ps,val) + set_result(ps,parm,val) + if is_filetype(ps.type) then + set_result(ps,parm..'_name',theArg) + end + if lapp.callback then + lapp.callback(parm,theArg,res) + end + i = i + 1 + val = nil + end + -- check unused parms, set defaults and check if any required parameters were missed + for parm,ps in pairs(parms) do + if not ps.used then + if ps.required then lapp.error("missing required parameter: "..parm) end + set_result(ps,parm,ps.defval) + end + end + return results +end + +if arg then + script = arg[0]:gsub('.+[\\/]',''):gsub('%.%a+$','') +else + script = "inter" +end + + +setmetatable(lapp, { + __call = function(tbl,str) return lapp.process_options_string(str) end, +}) + + +return lapp + + diff --git a/Utils/luarocks/share/lua/5.1/pl/lexer.lua b/Utils/luarocks/share/lua/5.1/pl/lexer.lua new file mode 100644 index 000000000..5b9fa0c80 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/lexer.lua @@ -0,0 +1,461 @@ +--- Lexical scanner for creating a sequence of tokens from text.
    +--

    lexer.scan(s) returns an iterator over all tokens found in the +-- string s. This iterator returns two values, a token type string +-- (such as 'string' for quoted string, 'iden' for identifier) and the value of the +-- token. +--

    +-- Versions specialized for Lua and C are available; these also handle block comments +-- and classify keywords as 'keyword' tokens. For example: +--

    +-- > s = 'for i=1,n do'
    +-- > for t,v in lexer.lua(s)  do print(t,v) end
    +-- keyword for
    +-- iden    i
    +-- =       =
    +-- number  1
    +-- ,       ,
    +-- iden    n
    +-- keyword do
    +-- 
    +-- See the Guide for further discussion
    +-- @class module +-- @name pl.lexer + +local yield,wrap = coroutine.yield,coroutine.wrap +local strfind = string.find +local strsub = string.sub +local append = table.insert +--[[ +module ('pl.lexer',utils._module) +]] + +local function assert_arg(idx,val,tp) + if type(val) ~= tp then + error("argument "..idx.." must be "..tp, 2) + end +end + +local lexer = {} + +local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' +local NUMBER2 = '^[%+%-]?%d+%.?%d*' +local NUMBER3 = '^0x[%da-fA-F]+' +local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' +local NUMBER5 = '^%d+%.?%d*' +local IDEN = '^[%a_][%w_]*' +local WSPACE = '^%s+' +local STRING1 = [[^'.-[^\\]']] +local STRING2 = [[^".-[^\\]"]] +local STRING3 = "^((['\"])%2)" -- empty string +local PREPRO = '^#.-[^\\]\n' + +local plain_matches,lua_matches,cpp_matches,lua_keyword,cpp_keyword + +local function tdump(tok) + return yield(tok,tok) +end + +local function ndump(tok,options) + if options and options.number then + tok = tonumber(tok) + end + return yield("number",tok) +end + +-- regular strings, single or double quotes; usually we want them +-- without the quotes +local function sdump(tok,options) + if options and options.string then + tok = tok:sub(2,-2) + end + return yield("string",tok) +end + +-- long Lua strings need extra work to get rid of the quotes +local function sdump_l(tok,options) + if options and options.string then + tok = tok:sub(3,-3) + end + return yield("string",tok) +end + +local function chdump(tok,options) + if options and options.string then + tok = tok:sub(2,-2) + end + return yield("char",tok) +end + +local function cdump(tok) + return yield('comment',tok) +end + +local function wsdump (tok) + return yield("space",tok) +end + +local function pdump (tok) + return yield('prepro',tok) +end + +local function plain_vdump(tok) + return yield("iden",tok) +end + +local function lua_vdump(tok) + if lua_keyword[tok] then + return yield("keyword",tok) + else + return yield("iden",tok) + end +end + +local function cpp_vdump(tok) + if cpp_keyword[tok] then + return yield("keyword",tok) + else + return yield("iden",tok) + end +end + +--- create a plain token iterator from a string or file-like object. +-- @param s the string +-- @param matches an optional match table (set of pattern-action pairs) +-- @param filter a table of token types to exclude, by default {space=true} +-- @param options a table of options; by default, {number=true,string=true}, +-- which means convert numbers and strip string quotes. +function lexer.scan (s,matches,filter,options) + --assert_arg(1,s,'string') + local file = type(s) ~= 'string' and s + filter = filter or {space=true} + options = options or {number=true,string=true} + if filter then + if filter.space then filter[wsdump] = true end + if filter.comments then + filter[cdump] = true + end + end + if not matches then + if not plain_matches then + plain_matches = { + {WSPACE,wsdump}, + {NUMBER3,ndump}, + {IDEN,plain_vdump}, + {NUMBER1,ndump}, + {NUMBER2,ndump}, + {STRING3,sdump}, + {STRING1,sdump}, + {STRING2,sdump}, + {'^.',tdump} + } + end + matches = plain_matches + end + local function lex () + local i1,i2,idx,res1,res2,tok,pat,fun,capt + local line = 1 + if file then s = file:read()..'\n' end + local sz = #s + local idx = 1 + --print('sz',sz) + while true do + for _,m in ipairs(matches) do + pat = m[1] + fun = m[2] + i1,i2 = strfind(s,pat,idx) + if i1 then + tok = strsub(s,i1,i2) + idx = i2 + 1 + if not (filter and filter[fun]) then + lexer.finished = idx > sz + res1,res2 = fun(tok,options) + end + if res1 then + local tp = type(res1) + -- insert a token list + if tp=='table' then + yield('','') + for _,t in ipairs(res1) do + yield(t[1],t[2]) + end + elseif tp == 'string' then -- or search up to some special pattern + i1,i2 = strfind(s,res1,idx) + if i1 then + tok = strsub(s,i1,i2) + idx = i2 + 1 + yield('',tok) + else + yield('','') + idx = sz + 1 + end + --if idx > sz then return end + else + yield(line,idx) + end + end + if idx > sz then + if file then + --repeat -- next non-empty line + line = line + 1 + s = file:read() + if not s then return end + --until not s:match '^%s*$' + s = s .. '\n' + idx ,sz = 1,#s + break + else + return + end + else break end + end + end + end + end + return wrap(lex) +end + +local function isstring (s) + return type(s) == 'string' +end + +--- insert tokens into a stream. +-- @param tok a token stream +-- @param a1 a string is the type, a table is a token list and +-- a function is assumed to be a token-like iterator (returns type & value) +-- @param a2 a string is the value +function lexer.insert (tok,a1,a2) + if not a1 then return end + local ts + if isstring(a1) and isstring(a2) then + ts = {{a1,a2}} + elseif type(a1) == 'function' then + ts = {} + for t,v in a1() do + append(ts,{t,v}) + end + else + ts = a1 + end + tok(ts) +end + +--- get everything in a stream upto a newline. +-- @param tok a token stream +-- @return a string +function lexer.getline (tok) + local t,v = tok('.-\n') + return v +end + +--- get current line number.
    +-- Only available if the input source is a file-like object. +-- @param tok a token stream +-- @return the line number and current column +function lexer.lineno (tok) + return tok(0) +end + +--- get the rest of the stream. +-- @param tok a token stream +-- @return a string +function lexer.getrest (tok) + local t,v = tok('.+') + return v +end + +--- get the Lua keywords as a set-like table. +-- So res["and"] etc would be true. +-- @return a table +function lexer.get_keywords () + if not lua_keyword then + lua_keyword = { + ["and"] = true, ["break"] = true, ["do"] = true, + ["else"] = true, ["elseif"] = true, ["end"] = true, + ["false"] = true, ["for"] = true, ["function"] = true, + ["if"] = true, ["in"] = true, ["local"] = true, ["nil"] = true, + ["not"] = true, ["or"] = true, ["repeat"] = true, + ["return"] = true, ["then"] = true, ["true"] = true, + ["until"] = true, ["while"] = true + } + end + return lua_keyword +end + + +--- create a Lua token iterator from a string or file-like object. +-- Will return the token type and value. +-- @param s the string +-- @param filter a table of token types to exclude, by default {space=true,comments=true} +-- @param options a table of options; by default, {number=true,string=true}, +-- which means convert numbers and strip string quotes. +function lexer.lua(s,filter,options) + filter = filter or {space=true,comments=true} + lexer.get_keywords() + if not lua_matches then + lua_matches = { + {WSPACE,wsdump}, + {NUMBER3,ndump}, + {IDEN,lua_vdump}, + {NUMBER4,ndump}, + {NUMBER5,ndump}, + {STRING3,sdump}, + {STRING1,sdump}, + {STRING2,sdump}, + {'^%-%-%[%[.-%]%]',cdump}, + {'^%-%-.-\n',cdump}, + {'^%[%[.-%]%]',sdump_l}, + {'^==',tdump}, + {'^~=',tdump}, + {'^<=',tdump}, + {'^>=',tdump}, + {'^%.%.%.',tdump}, + {'^%.%.',tdump}, + {'^.',tdump} + } + end + return lexer.scan(s,lua_matches,filter,options) +end + +--- create a C/C++ token iterator from a string or file-like object. +-- Will return the token type type and value. +-- @param s the string +-- @param filter a table of token types to exclude, by default {space=true,comments=true} +-- @param options a table of options; by default, {number=true,string=true}, +-- which means convert numbers and strip string quotes. +function lexer.cpp(s,filter,options) + filter = filter or {comments=true} + if not cpp_keyword then + cpp_keyword = { + ["class"] = true, ["break"] = true, ["do"] = true, ["sizeof"] = true, + ["else"] = true, ["continue"] = true, ["struct"] = true, + ["false"] = true, ["for"] = true, ["public"] = true, ["void"] = true, + ["private"] = true, ["protected"] = true, ["goto"] = true, + ["if"] = true, ["static"] = true, ["const"] = true, ["typedef"] = true, + ["enum"] = true, ["char"] = true, ["int"] = true, ["bool"] = true, + ["long"] = true, ["float"] = true, ["true"] = true, ["delete"] = true, + ["double"] = true, ["while"] = true, ["new"] = true, + ["namespace"] = true, ["try"] = true, ["catch"] = true, + ["switch"] = true, ["case"] = true, ["extern"] = true, + ["return"] = true,["default"] = true,['unsigned'] = true,['signed'] = true, + ["union"] = true, ["volatile"] = true, ["register"] = true,["short"] = true, + } + end + if not cpp_matches then + cpp_matches = { + {WSPACE,wsdump}, + {PREPRO,pdump}, + {NUMBER3,ndump}, + {IDEN,cpp_vdump}, + {NUMBER4,ndump}, + {NUMBER5,ndump}, + {STRING3,sdump}, + {STRING1,chdump}, + {STRING2,sdump}, + {'^//.-\n',cdump}, + {'^/%*.-%*/',cdump}, + {'^==',tdump}, + {'^!=',tdump}, + {'^<=',tdump}, + {'^>=',tdump}, + {'^->',tdump}, + {'^&&',tdump}, + {'^||',tdump}, + {'^%+%+',tdump}, + {'^%-%-',tdump}, + {'^%+=',tdump}, + {'^%-=',tdump}, + {'^%*=',tdump}, + {'^/=',tdump}, + {'^|=',tdump}, + {'^%^=',tdump}, + {'^::',tdump}, + {'^.',tdump} + } + end + return lexer.scan(s,cpp_matches,filter,options) +end + +--- get a list of parameters separated by a delimiter from a stream. +-- @param tok the token stream +-- @param endtoken end of list (default ')'). Can be '\n' +-- @param delim separator (default ',') +-- @return a list of token lists. +function lexer.get_separated_list(tok,endtoken,delim) + endtoken = endtoken or ')' + delim = delim or ',' + local parm_values = {} + local level = 1 -- used to count ( and ) + local tl = {} + local function tappend (tl,t,val) + val = val or t + append(tl,{t,val}) + end + local is_end + if endtoken == '\n' then + is_end = function(t,val) + return t == 'space' and val:find '\n' + end + else + is_end = function (t) + return t == endtoken + end + end + local token,value + while true do + token,value=tok() + if not token then return nil,'EOS' end -- end of stream is an error! + if is_end(token,value) and level == 1 then + append(parm_values,tl) + break + elseif token == '(' then + level = level + 1 + tappend(tl,'(') + elseif token == ')' then + level = level - 1 + if level == 0 then -- finished with parm list + append(parm_values,tl) + break + else + tappend(tl,')') + end + elseif token == delim and level == 1 then + append(parm_values,tl) -- a new parm + tl = {} + else + tappend(tl,token,value) + end + end + return parm_values,{token,value} +end + +--- get the next non-space token from the stream. +-- @param tok the token stream. +function lexer.skipws (tok) + local t,v = tok() + while t == 'space' do + t,v = tok() + end + return t,v +end + +local skipws = lexer.skipws + +--- get the next token, which must be of the expected type. +-- Throws an error if this type does not match! +-- @param tok the token stream +-- @param expected_type the token type +-- @param no_skip_ws whether we should skip whitespace +function lexer.expecting (tok,expected_type,no_skip_ws) + assert_arg(1,tok,'function') + assert_arg(2,expected_type,'string') + local t,v + if no_skip_ws then + t,v = tok() + else + t,v = skipws(tok) + end + if t ~= expected_type then error ("expecting "..expected_type,2) end + return v +end + +return lexer diff --git a/Utils/luarocks/share/lua/5.1/pl/luabalanced.lua b/Utils/luarocks/share/lua/5.1/pl/luabalanced.lua new file mode 100644 index 000000000..bb2377836 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/luabalanced.lua @@ -0,0 +1,264 @@ +--- Extract delimited Lua sequences from strings. +-- Inspired by Damian Conway's Text::Balanced in Perl.
    +--
      +--
    • [1] Lua Wiki Page
    • +--
    • [2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm
    • +--

    +--
    +-- local lb = require "pl.luabalanced"
    +-- --Extract Lua expression starting at position 4.
    +--  print(lb.match_expression("if x^2 + x > 5 then print(x) end", 4))
    +--  --> x^2 + x > 5     16
    +-- --Extract Lua string starting at (default) position 1.
    +-- print(lb.match_string([["test\"123" .. "more"]]))
    +-- --> "test\"123"     12
    +-- 
    +-- (c) 2008, David Manura, Licensed under the same terms as Lua (MIT license). +-- @class module +-- @name pl.luabalanced + +local M = {} + +local assert = assert +local table_concat = table.concat + +-- map opening brace <-> closing brace. +local ends = { ['('] = ')', ['{'] = '}', ['['] = ']' } +local begins = {}; for k,v in pairs(ends) do begins[v] = k end + + +-- Match Lua string in string starting at position . +-- Returns , , where is the matched +-- string (or nil on no match) and is the character +-- following the match (or on no match). +-- Supports all Lua string syntax: "...", '...', [[...]], [=[...]=], etc. +local function match_string(s, pos) + pos = pos or 1 + local posa = pos + local c = s:sub(pos,pos) + if c == '"' or c == "'" then + pos = pos + 1 + while 1 do + pos = assert(s:find("[" .. c .. "\\]", pos), 'syntax error') + if s:sub(pos,pos) == c then + local part = s:sub(posa, pos) + return part, pos + 1 + else + pos = pos + 2 + end + end + else + local sc = s:match("^%[(=*)%[", pos) + if sc then + local _; _, pos = s:find("%]" .. sc .. "%]", pos) + assert(pos) + local part = s:sub(posa, pos) + return part, pos + 1 + else + return nil, pos + end + end +end +M.match_string = match_string + + +-- Match bracketed Lua expression, e.g. "(...)", "{...}", "[...]", "[[...]]", +-- [=[...]=], etc. +-- Function interface is similar to match_string. +local function match_bracketed(s, pos) + pos = pos or 1 + local posa = pos + local ca = s:sub(pos,pos) + if not ends[ca] then + return nil, pos + end + local stack = {} + while 1 do + pos = s:find('[%(%{%[%)%}%]\"\']', pos) + assert(pos, 'syntax error: unbalanced') + local c = s:sub(pos,pos) + if c == '"' or c == "'" then + local part; part, pos = match_string(s, pos) + assert(part) + elseif ends[c] then -- open + local mid, posb + if c == '[' then mid, posb = s:match('^%[(=*)%[()', pos) end + if mid then + pos = s:match('%]' .. mid .. '%]()', posb) + assert(pos, 'syntax error: long string not terminated') + if #stack == 0 then + local part = s:sub(posa, pos-1) + return part, pos + end + else + stack[#stack+1] = c + pos = pos + 1 + end + else -- close + assert(stack[#stack] == assert(begins[c]), 'syntax error: unbalanced') + stack[#stack] = nil + if #stack == 0 then + local part = s:sub(posa, pos) + return part, pos+1 + end + pos = pos + 1 + end + end +end +M.match_bracketed = match_bracketed + + +-- Match Lua comment, e.g. "--...\n", "--[[...]]", "--[=[...]=]", etc. +-- Function interface is similar to match_string. +local function match_comment(s, pos) + pos = pos or 1 + if s:sub(pos, pos+1) ~= '--' then + return nil, pos + end + pos = pos + 2 + local partt, post = match_string(s, pos) + if partt then + return '--' .. partt, post + end + local part; part, pos = s:match('^([^\n]*\n?)()', pos) + return '--' .. part, pos +end + + +-- Match Lua expression, e.g. "a + b * c[e]". +-- Function interface is similar to match_string. +local wordop = {['and']=true, ['or']=true, ['not']=true} +local is_compare = {['>']=true, ['<']=true, ['~']=true} +local function match_expression(s, pos) + pos = pos or 1 + local posa = pos + local lastident + local poscs, posce + while pos do + local c = s:sub(pos,pos) + if c == '"' or c == "'" or c == '[' and s:find('^[=%[]', pos+1) then + local part; part, pos = match_string(s, pos) + assert(part, 'syntax error') + elseif c == '-' and s:sub(pos+1,pos+1) == '-' then + -- note: handle adjacent comments in loop to properly support + -- backtracing (poscs/posce). + poscs = pos + while s:sub(pos,pos+1) == '--' do + local part; part, pos = match_comment(s, pos) + assert(part) + pos = s:match('^%s*()', pos) + posce = pos + end + elseif c == '(' or c == '{' or c == '[' then + local part; part, pos = match_bracketed(s, pos) + elseif c == '=' and s:sub(pos+1,pos+1) == '=' then + pos = pos + 2 -- skip over two-char op containing '=' + elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then + pos = pos + 1 -- skip over two-char op containing '=' + elseif c:match'^[%)%}%];,=]' then + local part = s:sub(posa, pos-1) + return part, pos + elseif c:match'^[%w_]' then + local newident,newpos = s:match('^([%w_]+)()', pos) + if pos ~= posa and not wordop[newident] then -- non-first ident + local pose = ((posce == pos) and poscs or pos) - 1 + while s:match('^%s', pose) do pose = pose - 1 end + local ce = s:sub(pose,pose) + if ce:match'[%)%}\'\"%]]' or + ce:match'[%w_]' and not wordop[lastident] + then + local part = s:sub(posa, pos-1) + return part, pos + end + end + lastident, pos = newident, newpos + else + pos = pos + 1 + end + pos = s:find('[%(%{%[%)%}%]\"\';,=%w_%-]', pos) + end + local part = s:sub(posa, #s) + return part, #s+1 +end +M.match_expression = match_expression + + +-- Match name list (zero or more names). E.g. "a,b,c" +-- Function interface is similar to match_string, +-- but returns array as match. +local function match_namelist(s, pos) + pos = pos or 1 + local list = {} + while 1 do + local c = #list == 0 and '^' or '^%s*,%s*' + local item, post = s:match(c .. '([%a_][%w_]*)%s*()', pos) + if item then pos = post else break end + list[#list+1] = item + end + return list, pos +end +M.match_namelist = match_namelist + + +-- Match expression list (zero or more expressions). E.g. "a+b,b*c". +-- Function interface is similar to match_string, +-- but returns array as match. +local function match_explist(s, pos) + pos = pos or 1 + local list = {} + while 1 do + if #list ~= 0 then + local post = s:match('^%s*,%s*()', pos) + if post then pos = post else break end + end + local item; item, pos = match_expression(s, pos) + assert(item, 'syntax error') + list[#list+1] = item + end + return list, pos +end +M.match_explist = match_explist + + +-- Replace snippets of code in Lua code string +-- using replacement function f(u,sin) --> sout. +-- is the type of snippet ('c' = comment, 's' = string, +-- 'e' = any other code). +-- Snippet is replaced with (unless is nil or false, in +-- which case the original snippet is kept) +-- This is somewhat analogous to string.gsub . +local function gsub(s, f) + local pos = 1 + local posa = 1 + local sret = '' + while 1 do + pos = s:find('[%-\'\"%[]', pos) + if not pos then break end + if s:match('^%-%-', pos) then + local exp = s:sub(posa, pos-1) + if #exp > 0 then sret = sret .. (f('e', exp) or exp) end + local comment; comment, pos = match_comment(s, pos) + sret = sret .. (f('c', assert(comment)) or comment) + posa = pos + else + local posb = s:find('^[\'\"%[]', pos) + local str + if posb then str, pos = match_string(s, posb) end + if str then + local exp = s:sub(posa, posb-1) + if #exp > 0 then sret = sret .. (f('e', exp) or exp) end + sret = sret .. (f('s', str) or str) + posa = pos + else + pos = pos + 1 + end + end + end + local exp = s:sub(posa) + if #exp > 0 then sret = sret .. (f('e', exp) or exp) end + return sret +end +M.gsub = gsub + + +return M diff --git a/Utils/luarocks/share/lua/5.1/pl/operator.lua b/Utils/luarocks/share/lua/5.1/pl/operator.lua new file mode 100644 index 000000000..48c8a10b4 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/operator.lua @@ -0,0 +1,197 @@ +--- Lua operators available as functions. +-- (similar to the Python module of the same name)
    +-- There is a module field optable which maps the operator strings +-- onto these functions, e.g.
    operator.optable['()']==operator.call
    +--

    Operator strings like '>' and '{}' can be passed to most Penlight functions +-- expecting a function argument.

    +-- @class module +-- @name pl.operator + +local strfind = string.find +local utils = require 'pl.utils' + +local operator = {} + +--- apply function to some arguments () +-- @param fn a function or callable object +-- @param ... arguments +function operator.call(fn,...) + return fn(...) +end + +--- get the indexed value from a table [] +-- @param t a table or any indexable object +-- @param k the key +function operator.index(t,k) + return t[k] +end + +--- returns true if arguments are equal == +-- @param a value +-- @param b value +function operator.eq(a,b) + return a==b +end + +--- returns true if arguments are not equal ~= + -- @param a value +-- @param b value +function operator.neq(a,b) + return a~=b +end + +--- returns true if a is less than b < +-- @param a value +-- @param b value +function operator.lt(a,b) + return a < b +end + +--- returns true if a is less or equal to b <= +-- @param a value +-- @param b value +function operator.le(a,b) + return a <= b +end + +--- returns true if a is greater than b > +-- @param a value +-- @param b value +function operator.gt(a,b) + return a > b +end + +--- returns true if a is greater or equal to b >= +-- @param a value +-- @param b value +function operator.ge(a,b) + return a >= b +end + +--- returns length of string or table # +-- @param a a string or a table +function operator.len(a) + return #a +end + +--- add two values + +-- @param a value +-- @param b value +function operator.add(a,b) + return a+b +end + +--- subtract b from a - +-- @param a value +-- @param b value +function operator.sub(a,b) + return a-b +end + +--- multiply two values * +-- @param a value +-- @param b value +function operator.mul(a,b) + return a*b +end + +--- divide first value by second / +-- @param a value +-- @param b value +function operator.div(a,b) + return a/b +end + +--- raise first to the power of second ^ +-- @param a value +-- @param b value +function operator.pow(a,b) + return a^b +end + +--- modulo; remainder of a divided by b % +-- @param a value +-- @param b value +function operator.mod(a,b) + return a%b +end + +--- concatenate two values (either strings or __concat defined) .. +-- @param a value +-- @param b value +function operator.concat(a,b) + return a..b +end + +--- return the negative of a value - +-- @param a value +-- @param b value +function operator.unm(a) + return -a +end + +--- false if value evaluates as true not +-- @param a value +function operator.lnot(a) + return not a +end + +--- true if both values evaluate as true and +-- @param a value +-- @param b value +function operator.land(a,b) + return a and b +end + +--- true if either value evaluate as true or +-- @param a value +-- @param b value +function operator.lor(a,b) + return a or b +end + +--- make a table from the arguments {} +-- @param ... non-nil arguments +-- @return a table +function operator.table (...) + return {...} +end + +--- match two strings ~ +-- uses @{string.find} +function operator.match (a,b) + return strfind(a,b)~=nil +end + +--- the null operation. +-- @param ... arguments +-- @return the arguments +function operator.nop (...) + return ... +end + + operator.optable = { + ['+']=operator.add, + ['-']=operator.sub, + ['*']=operator.mul, + ['/']=operator.div, + ['%']=operator.mod, + ['^']=operator.pow, + ['..']=operator.concat, + ['()']=operator.call, + ['[]']=operator.index, + ['<']=operator.lt, + ['<=']=operator.le, + ['>']=operator.gt, + ['>=']=operator.ge, + ['==']=operator.eq, + ['~=']=operator.neq, + ['#']=operator.len, + ['and']=operator.land, + ['or']=operator.lor, + ['{}']=operator.table, + ['~']=operator.match, + ['']=operator.nop, +} + +return operator diff --git a/Utils/luarocks/share/lua/5.1/pl/path.lua b/Utils/luarocks/share/lua/5.1/pl/path.lua new file mode 100644 index 000000000..9055f3f4b --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/path.lua @@ -0,0 +1,335 @@ +--- Path manipulation and file queries.
    +-- This is modelled after Python's os.path library (11.1) +-- @class module +-- @name pl.path + +-- imports and locals +local _G = _G +local sub = string.sub +local getenv = os.getenv +local tmpnam = os.tmpname +local attributes, currentdir, link_attrib +local package = package +local io = io +local append = table.insert +local ipairs = ipairs +local utils = require 'pl.utils' +local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise + +--[[ +module ('pl.path',utils._module) +]] + +local path, attrib + +if _G.luajava then + path = require 'pl.platf.luajava' +else + path = {} + + local res,lfs = _G.pcall(_G.require,'lfs') + if res then + attributes = lfs.attributes + currentdir = lfs.currentdir + link_attrib = lfs.symlinkattributes + else + error("pl.path requires LuaFileSystem") + end + + attrib = attributes + path.attrib = attrib + path.link_attrib = link_attrib + path.dir = lfs.dir + path.mkdir = lfs.mkdir + path.rmdir = lfs.rmdir + path.chdir = lfs.chdir + + --- is this a directory? + -- @param P A file path + function path.isdir(P) + if P:match("\\$") then + P = P:sub(1,-2) + end + return attrib(P,'mode') == 'directory' + end + + --- is this a file?. + -- @param P A file path + function path.isfile(P) + return attrib(P,'mode') == 'file' + end + + -- is this a symbolic link? + -- @param P A file path + function path.islink(P) + if link_attrib then + return link_attrib(P,'mode')=='link' + else + return false + end + end + + --- return size of a file. + -- @param P A file path + function path.getsize(P) + return attrib(P,'size') + end + + --- does a path exist?. + -- @param P A file path + -- @return the file path if it exists, nil otherwise + function path.exists(P) + return attrib(P,'mode') ~= nil and P + end + + --- Return the time of last access as the number of seconds since the epoch. + -- @param P A file path + function path.getatime(P) + return attrib(P,'access') + end + + --- Return the time of last modification + -- @param P A file path + function path.getmtime(P) + return attrib(P,'modification') + end + + ---Return the system's ctime. + -- @param P A file path + function path.getctime(P) + return path.attrib(P,'change') + end +end + + +local function at(s,i) + return sub(s,i,i) +end + +path.is_windows = utils.dir_separator == '\\' + +local other_sep +-- !constant sep is the directory separator for this platform. +if path.is_windows then + path.sep = '\\'; other_sep = '/' + path.dirsep = ';' +else + path.sep = '/' + path.dirsep = ':' +end +local sep,dirsep = path.sep,path.dirsep + +--- are we running Windows? +-- @class field +-- @name path.is_windows + +--- path separator for this platform. +-- @class field +-- @name path.sep + +--- separator for PATH for this platform +-- @class field +-- @name path.dirsep + +--- given a path, return the directory part and a file part. +-- if there's no directory part, the first value will be empty +-- @param P A file path +function path.splitpath(P) + assert_string(1,P) + local i = #P + local ch = at(P,i) + while i > 0 and ch ~= sep and ch ~= other_sep do + i = i - 1 + ch = at(P,i) + end + if i == 0 then + return '',P + else + return sub(P,1,i-1), sub(P,i+1) + end +end + +--- return an absolute path. +-- @param P A file path +function path.abspath(P) + assert_string(1,P) + if not currentdir then return P end + P = P:gsub('[\\/]$','') + local pwd = currentdir() + if not path.isabs(P) then + return path.join(pwd,P) + elseif path.is_windows and at(P,2) ~= ':' and at(P,2) ~= '\\' then + return pwd:sub(1,2)..P + else + return P + end +end + +--- given a path, return the root part and the extension part. +-- if there's no extension part, the second value will be empty +-- @param P A file path +function path.splitext(P) + assert_string(1,P) + local i = #P + local ch = at(P,i) + while i > 0 and ch ~= '.' do + if ch == sep or ch == other_sep then + return P,'' + end + i = i - 1 + ch = at(P,i) + end + if i == 0 then + return P,'' + else + return sub(P,1,i-1),sub(P,i) + end +end + +--- return the directory part of a path +-- @param P A file path +function path.dirname(P) + assert_string(1,P) + local p1,p2 = path.splitpath(P) + return p1 +end + +--- return the file part of a path +-- @param P A file path +function path.basename(P) + assert_string(1,P) + local p1,p2 = path.splitpath(P) + return p2 +end + +--- get the extension part of a path. +-- @param P A file path +function path.extension(P) + assert_string(1,P) + local p1,p2 = path.splitext(P) + return p2 +end + +--- is this an absolute path?. +-- @param P A file path +function path.isabs(P) + assert_string(1,P) + if path.is_windows then + return at(P,1) == '/' or at(P,1)=='\\' or at(P,2)==':' + else + return at(P,1) == '/' + end +end + +--- return the P resulting from combining the two paths. +-- if the second is already an absolute path, then it returns it. +-- @param p1 A file path +-- @param p2 A file path +function path.join(p1,p2) + assert_string(1,p1) + assert_string(2,p2) + if path.isabs(p2) then return p2 end + local endc = at(p1,#p1) + if endc ~= path.sep and endc ~= other_sep then + p1 = p1..path.sep + end + return p1..p2 +end + +--- normalize the case of a pathname. On Unix, this returns the path unchanged; +-- for Windows, it converts the path to lowercase, and it also converts forward slashes +-- to backward slashes. +-- @param P A file path +function path.normcase(P) + assert_string(1,P) + if path.is_windows then + return (P:lower():gsub('/','\\')) + else + return P + end +end + +--- normalize a path name. +-- A//B, A/./B and A/foo/../B all become A/B. +-- @param P a file path +function path.normpath (P) + assert_string(1,P) + if path.is_windows then + P = P:gsub('/','\\') + return (P:gsub('[^\\]+\\%.%.\\',''):gsub('\\%.?\\','\\')) + else + return (P:gsub('[^/]+/%.%./',''):gsub('/%.?/','/')) + end +end + + +--- Replace a starting '~' with the user's home directory. +-- In windows, if HOME isn't set, then USERPROFILE is used in preference to +-- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. +-- @param P A file path +function path.expanduser(P) + assert_string(1,P) + if at(P,1) == '~' then + local home = getenv('HOME') + if not home then -- has to be Windows + home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH') + end + return home..sub(P,2) + else + return P + end +end + + +---Return a suitable full path to a new temporary file name. +-- unlike os.tmpnam(), it always gives you a writeable path (uses %TMP% on Windows) +function path.tmpname () + local res = tmpnam() + if path.is_windows then res = getenv('TMP')..res end + return res +end + +--- return the largest common prefix path of two paths. +-- @param path1 a file path +-- @param path2 a file path +function path.common_prefix (path1,path2) + assert_string(1,path1) + assert_string(2,path2) + -- get them in order! + if #path1 > #path2 then path2,path1 = path1,path2 end + for i = 1,#path1 do + local c1 = at(path1,i) + if c1 ~= at(path2,i) then + local cp = path1:sub(1,i-1) + if at(path1,i-1) ~= sep then + cp = path.dirname(cp) + end + return cp + end + end + if at(path2,#path1+1) ~= sep then path1 = path.dirname(path1) end + return path1 + --return '' +end + + +--- return the full path where a particular Lua module would be found. +-- Both package.path and package.cpath is searched, so the result may +-- either be a Lua file or a shared libarary. +-- @param mod name of the module +-- @return on success: path of module, lua or binary +-- @return on error: nil,error string +function path.package_path(mod) + assert_string(1,mod) + local res + mod = mod:gsub('%.',sep) + res = package.searchpath(mod,package.path) + if res then return res,true end + res = package.searchpath(mod,package.cpath) + if res then return res,false end + return raise 'cannot find module on path' +end + + +---- finis ----- +return path diff --git a/Utils/luarocks/share/lua/5.1/pl/permute.lua b/Utils/luarocks/share/lua/5.1/pl/permute.lua new file mode 100644 index 000000000..8c2e66fa9 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/permute.lua @@ -0,0 +1,65 @@ +--- Permutation operations. +-- @class module +-- @name pl.permute +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local copy = tablex.deepcopy +local append = table.insert +local coroutine = coroutine +local resume = coroutine.resume +local assert_arg = utils.assert_arg + +--[[ +module ('pl.permute',utils._module) +]] + +local permute = {} + +-- PiL, 9.3 + +local permgen +permgen = function (a, n, fn) + if n == 0 then + fn(a) + else + for i=1,n do + -- put i-th element as the last one + a[n], a[i] = a[i], a[n] + + -- generate all permutations of the other elements + permgen(a, n - 1, fn) + + -- restore i-th element + a[n], a[i] = a[i], a[n] + + end + end +end + +--- an iterator over all permutations of the elements of a list. +-- Please note that the same list is returned each time, so do not keep references! +-- @param a list-like table +-- @return an iterator which provides the next permutation as a list +function permute.iter (a) + assert_arg(1,a,'table') + local n = #a + local co = coroutine.create(function () permgen(a, n, coroutine.yield) end) + return function () -- iterator + local code, res = resume(co) + return res + end +end + +--- construct a table containing all the permutations of a list. +-- @param a list-like table +-- @return a table of tables +-- @usage permute.table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}} +function permute.table (a) + assert_arg(1,a,'table') + local res = {} + local n = #a + permgen(a,n,function(t) append(res,copy(t)) end) + return res +end + +return permute diff --git a/Utils/luarocks/share/lua/5.1/pl/platf/luajava.lua b/Utils/luarocks/share/lua/5.1/pl/platf/luajava.lua new file mode 100644 index 000000000..4fb82e619 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/platf/luajava.lua @@ -0,0 +1,101 @@ +-- experimental support for LuaJava +-- +local path = {} + + +path.link_attrib = nil + +local File = luajava.bindClass("java.io.File") +local Array = luajava.bindClass('java.lang.reflect.Array') + +local function file(s) + return luajava.new(File,s) +end + +function path.dir(P) + local ls = file(P):list() + print(ls) + local idx,n = -1,Array:getLength(ls) + return function () + idx = idx + 1 + if idx == n then return nil + else + return Array:get(ls,idx) + end + end +end + +function path.mkdir(P) + return file(P):mkdir() +end + +function path.rmdir(P) + return file(P):delete() +end + +--- is this a directory? +-- @param P A file path +function path.isdir(P) + if P:match("\\$") then + P = P:sub(1,-2) + end + return file(P):isDirectory() +end + +--- is this a file?. +-- @param P A file path +function path.isfile(P) + return file(P):isFile() +end + +-- is this a symbolic link? +-- Direct support for symbolic links is not provided. +-- see http://stackoverflow.com/questions/813710/java-1-6-determine-symbolic-links +-- and the caveats therein. +-- @param P A file path +function path.islink(P) + local f = file(P) + local canon + local parent = f:getParent() + if not parent then + canon = f + else + parent = f.getParentFile():getCanonicalFile() + canon = luajava.new(File,parent,f:getName()) + end + return canon:getCanonicalFile() ~= canon:getAbsoluteFile() +end + +--- return size of a file. +-- @param P A file path +function path.getsize(P) + return file(P):length() +end + +--- does a path exist?. +-- @param P A file path +-- @return the file path if it exists, nil otherwise +function path.exists(P) + return file(P):exists() and P +end + +--- Return the time of last access as the number of seconds since the epoch. +-- @param P A file path +function path.getatime(P) + return path.getmtime(P) +end + +--- Return the time of last modification +-- @param P A file path +function path.getmtime(P) + -- Java time is no. of millisec since the epoch + return file(P):lastModified()/1000 +end + +---Return the system's ctime. +-- @param P A file path +function path.getctime(P) + return path.getmtime(P) +end + +return path diff --git a/Utils/luarocks/share/lua/5.1/pl/pretty.lua b/Utils/luarocks/share/lua/5.1/pl/pretty.lua new file mode 100644 index 000000000..c414c9d5c --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/pretty.lua @@ -0,0 +1,224 @@ +--- Pretty-printing Lua tables. +-- Also provides a sandboxed Lua table reader and +-- a function to present large numbers in human-friendly format. +-- @class module +-- @name pl.pretty + +local append = table.insert +local concat = table.concat +local utils = require 'pl.utils' +local lexer = require 'pl.lexer' +local assert_arg = utils.assert_arg + +local pretty = {} + +--- read a string representation of a Lua table. +-- Uses load(), but tries to be cautious about loading arbitrary code! +-- It is expecting a string of the form '{...}', with perhaps some whitespace +-- before or after the curly braces. An empty environment is used, and +-- any occurance of the keyword 'function' will be considered a problem. +-- @param s {string} string of the form '{...}', with perhaps some whitespace +-- before or after the curly braces. +function pretty.read(s) + assert_arg(1,s,'string') + if not s:find '^%s*%b{}%s*$' then return nil,"not a Lua table" end + if s:find '[^\'"%w_]function[^\'"%w_]' then + local tok = lexer.lua(s) + for t,v in tok do + if t == 'keyword' then + return nil,"cannot have Lua keywords in table definition" + end + end + end + local chunk,err = utils.load('return '..s,'tbl','t',{}) + if not chunk then return nil,err end + return chunk() +end + +local function quote_if_necessary (v) + if not v then return '' + else + if v:find ' ' then v = '"'..v..'"' end + end + return v +end + +local keywords + + +--- Create a string representation of a Lua table. +-- This function never fails, but may complain by returning an +-- extra value. Normally puts out one item per line, using +-- the provided indent; set the second parameter to '' if +-- you want output on one line. +-- @param tbl {table} Table to serialize to a string. +-- @param space {string} (optional) The indent to use. +-- Defaults to two spaces. +-- @param not_clever {bool} (optional) Use for plain output, e.g {['key']=1}. +-- Defaults to false. +-- @return a string +-- @return a possible error message +function pretty.write (tbl,space,not_clever) + if type(tbl) ~= 'table' then + local res = tostring(tbl) + if type(tbl) == 'string' then res = '"'..res..'"' end + return res, 'not a table' + end + if not keywords then + keywords = lexer.get_keywords() + end + local set = ' = ' + if space == '' then set = '=' end + space = space or ' ' + local lines = {} + local line = '' + local tables = {} + + local function is_identifier (s) + return (s:find('^[%a_][%w_]*$')) and not keywords[s] + end + + local function put(s) + if #s > 0 then + line = line..s + end + end + + local function putln (s) + if #line > 0 then + line = line..s + append(lines,line) + line = '' + else + append(lines,s) + end + end + + local function eat_last_comma () + local n,lastch = #lines + local lastch = lines[n]:sub(-1,-1) + if lastch == ',' then + lines[n] = lines[n]:sub(1,-2) + end + end + + local function quote (s) + return ('%q'):format(tostring(s)) + end + + local function index (numkey,key) + if not numkey then key = quote(key) end + return '['..key..']' + end + + local writeit + writeit = function (t,oldindent,indent) + local tp = type(t) + if tp ~= 'string' and tp ~= 'table' then + putln(quote_if_necessary(tostring(t))..',') + elseif tp == 'string' then + if t:find('\n') then + putln('[[\n'..t..']],') + else + putln(quote(t)..',') + end + elseif tp == 'table' then + if tables[t] then + putln(',') + return + end + tables[t] = true + local newindent = indent..space + putln('{') + local used = {} + if not not_clever then + for i,val in ipairs(t) do + put(indent) + writeit(val,indent,newindent) + used[i] = true + end + end + for key,val in pairs(t) do + local numkey = type(key) == 'number' + if not_clever then + key = tostring(key) + put(indent..index(numkey,key)..set) + writeit(val,indent,newindent) + else + if not numkey or not used[key] then -- non-array indices + if numkey or not is_identifier(key) then + key = index(numkey,key) + end + put(indent..key..set) + writeit(val,indent,newindent) + end + end + end + eat_last_comma() + putln(oldindent..'},') + else + putln(tostring(t)..',') + end + end + writeit(tbl,'',space) + eat_last_comma() + return concat(lines,#space > 0 and '\n' or '') +end + +--- Dump a Lua table out to a file or stdout. +-- @param t {table} The table to write to a file or stdout. +-- @param ... {string} (optional) File name to write too. Defaults to writing +-- to stdout. +function pretty.dump (t,...) + if select('#',...)==0 then + print(pretty.write(t)) + return true + else + return utils.writefile(...,pretty.write(t)) + end +end + +local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'} + +local comma +function comma (val) + local thou = math.floor(val/1000) + if thou > 0 then return comma(thou)..','..(val % 1000) + else return tostring(val) end +end + +--- format large numbers nicely for human consumption. +-- @param num a number +-- @param kind one of 'M' (memory in KiB etc), 'N' (postfixes are 'K','M' and 'B') +-- and 'T' (use commas as thousands separator) +-- @param prec number of digits to use for 'M' and 'N' (default 1) +function pretty.number (num,kind,prec) + local fmt = '%.'..(prec or 1)..'f%s' + if kind == 'T' then + return comma(num) + else + local postfixes, fact + if kind == 'M' then + fact = 1024 + postfixes = memp + else + fact = 1000 + postfixes = nump + end + local div = fact + local k = 1 + while num >= div and k <= #postfixes do + div = div * fact + k = k + 1 + end + div = div / fact + if k > #postfixes then k = k - 1; div = div/fact end + if k > 1 then + return fmt:format(num/div,postfixes[k] or 'duh') + else + return num..postfixes[1] + end + end +end + +return pretty diff --git a/Utils/luarocks/share/lua/5.1/pl/seq.lua b/Utils/luarocks/share/lua/5.1/pl/seq.lua new file mode 100644 index 000000000..479a12a6e --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/seq.lua @@ -0,0 +1,527 @@ +--- Manipulating sequences as iterators. +-- @class module +-- @name pl.seq + +local next,assert,type,pairs,tonumber,type,setmetatable,getmetatable,_G = next,assert,type,pairs,tonumber,type,setmetatable,getmetatable,_G +local strfind = string.find +local strmatch = string.match +local format = string.format +local mrandom = math.random +local remove,tsort,tappend = table.remove,table.sort,table.insert +local io = io +local utils = require 'pl.utils' +local function_arg = utils.function_arg +local _List = utils.stdmt.List +local _Map = utils.stdmt.Map +local assert_arg = utils.assert_arg +require 'debug' + +--[[ +module("pl.seq",utils._module) +]] + +local seq = {} + +-- given a number, return a function(y) which returns true if y > x +-- @param x a number +function seq.greater_than(x) + return function(v) + return tonumber(v) > x + end +end + +-- given a number, returns a function(y) which returns true if y < x +-- @param x a number +function seq.less_than(x) + return function(v) + return tonumber(v) < x + end +end + +-- given any value, return a function(y) which returns true if y == x +-- @param x a value +function seq.equal_to(x) + if type(x) == "number" then + return function(v) + return tonumber(v) == x + end + else + return function(v) + return v == x + end + end +end + +--- given a string, return a function(y) which matches y against the string. +-- @param s a string +function seq.matching(s) + return function(v) + return strfind(v,s) + end +end + +--- sequence adaptor for a table. Note that if any generic function is +-- passed a table, it will automatically use seq.list() +-- @param t a list-like table +-- @usage sum(list(t)) is the sum of all elements of t +-- @usage for x in list(t) do...end +function seq.list(t) + assert_arg(1,t,'table') + local key,value + return function() + key,value = next(t,key) + return value + end +end + +--- return the keys of the table. +-- @param t a list-like table +-- @return iterator over keys +function seq.keys(t) + assert_arg(1,t,'table') + local key,value + return function() + key,value = next(t,key) + return key + end +end + +local list = seq.list +local function default_iter(iter) + if type(iter) == 'table' then return list(iter) + else return iter end +end + +seq.iter = default_iter + +--- create an iterator over a numerical range. Like the standard Python function xrange. +-- @param start a number +-- @param finish a number greater than start +function seq.range(start,finish) + local i = start - 1 + return function() + i = i + 1 + if i > finish then return nil + else return i end + end +end + +-- count the number of elements in the sequence which satisfy the predicate +-- @param iter a sequence +-- @param condn a predicate function (must return either true or false) +-- @param optional argument to be passed to predicate as second argument. +function seq.count(iter,condn,arg) + local i = 0 + seq.foreach(iter,function(val) + if condn(val,arg) then i = i + 1 end + end) + return i +end + +--- return the minimum and the maximum value of the sequence. +-- @param iter a sequence +function seq.minmax(iter) + local vmin,vmax = 1e70,-1e70 + for v in default_iter(iter) do + v = tonumber(v) + if v < vmin then vmin = v end + if v > vmax then vmax = v end + end + return vmin,vmax +end + +--- return the sum and element count of the sequence. +-- @param iter a sequence +-- @param fn an optional function to apply to the values +function seq.sum(iter,fn) + local s = 0 + local i = 0 + for v in default_iter(iter) do + if fn then v = fn(v) end + s = s + v + i = i + 1 + end + return s,i +end + +--- create a table from the sequence. (This will make the result a List.) +-- @param iter a sequence +-- @return a List +-- @usage copy(list(ls)) is equal to ls +-- @usage copy(list {1,2,3}) == List{1,2,3} +function seq.copy(iter) + local res = {} + for v in default_iter(iter) do + tappend(res,v) + end + setmetatable(res,_List) + return res +end + +--- create a table of pairs from the double-valued sequence. +-- @param iter a double-valued sequence +-- @param i1 used to capture extra iterator values +-- @param i2 as with pairs & ipairs +-- @usage copy2(ipairs{10,20,30}) == {{1,10},{2,20},{3,30}} +-- @return a list-like table +function seq.copy2 (iter,i1,i2) + local res = {} + for v1,v2 in iter,i1,i2 do + tappend(res,{v1,v2}) + end + return res +end + +--- create a table of 'tuples' from a multi-valued sequence. +-- A generalization of copy2 above +-- @param iter a multiple-valued sequence +-- @return a list-like table +function seq.copy_tuples (iter) + iter = default_iter(iter) + local res = {} + local row = {iter()} + while #row > 0 do + tappend(res,row) + row = {iter()} + end + return res +end + +--- return an iterator of random numbers. +-- @param n the length of the sequence +-- @param l same as the first optional argument to math.random +-- @param u same as the second optional argument to math.random +-- @return a sequnce +function seq.random(n,l,u) + local rand + assert(type(n) == 'number') + if u then + rand = function() return mrandom(l,u) end + elseif l then + rand = function() return mrandom(l) end + else + rand = mrandom + end + + return function() + if n == 0 then return nil + else + n = n - 1 + return rand() + end + end +end + +--- return an iterator to the sorted elements of a sequence. +-- @param iter a sequence +-- @param comp an optional comparison function (comp(x,y) is true if x < y) +function seq.sort(iter,comp) + local t = seq.copy(iter) + tsort(t,comp) + return list(t) +end + +--- return an iterator which returns elements of two sequences. +-- @param iter1 a sequence +-- @param iter2 a sequence +-- @usage for x,y in seq.zip(ls1,ls2) do....end +function seq.zip(iter1,iter2) + iter1 = default_iter(iter1) + iter2 = default_iter(iter2) + return function() + return iter1(),iter2() + end +end + +--- A table where the key/values are the values and value counts of the sequence. +-- This version works with 'hashable' values like strings and numbers.
    +-- pl.tablex.count_map is more general. +-- @param iter a sequence +-- @return a map-like table +-- @return a table +-- @see pl.tablex.count_map +function seq.count_map(iter) + local t = {} + local v + for s in default_iter(iter) do + v = t[s] + if v then t[s] = v + 1 + else t[s] = 1 end + end + return setmetatable(t,_Map) +end + +-- given a sequence, return all the unique values in that sequence. +-- @param iter a sequence +-- @param returns_table true if we return a table, not a sequence +-- @return a sequence or a table; defaults to a sequence. +function seq.unique(iter,returns_table) + local t = seq.count_map(iter) + local res = {} + for k in pairs(t) do tappend(res,k) end + table.sort(res) + if returns_table then + return res + else + return list(res) + end +end + +-- print out a sequence @iter, with a separator @sep (default space) +-- and maximum number of values per line @nfields (default 7) +-- @fmt is an optional format function to create a representation of each value. +function seq.printall(iter,sep,nfields,fmt) + local write = io.write + if not sep then sep = ' ' end + if not nfields then + if sep == '\n' then nfields = 1e30 + else nfields = 7 end + end + if fmt then + local fstr = fmt + fmt = function(v) return format(fstr,v) end + end + local k = 1 + for v in default_iter(iter) do + if fmt then v = fmt(v) end + if k < nfields then + write(v,sep) + k = k + 1 + else + write(v,'\n') + k = 1 + end + end + write '\n' +end + +-- return an iterator running over every element of two sequences (concatenation). +-- @param iter1 a sequence +-- @param iter2 a sequence +function seq.splice(iter1,iter2) + iter1 = default_iter(iter1) + iter2 = default_iter(iter2) + local iter = iter1 + return function() + local ret = iter() + if ret == nil then + if iter == iter1 then + iter = iter2 + return iter() + else return nil end + else + return ret + end + end +end + +--- return a sequence where every element of a sequence has been transformed +-- by a function. If you don't supply an argument, then the function will +-- receive both values of a double-valued sequence, otherwise behaves rather like +-- tablex.map. +-- @param fn a function to apply to elements; may take two arguments +-- @param iter a sequence of one or two values +-- @param arg optional argument to pass to function. +function seq.map(fn,iter,arg) + fn = function_arg(1,fn) + iter = default_iter(iter) + return function() + local v1,v2 = iter() + if v1 == nil then return nil end + if arg then return fn(v1,arg) or false + else return fn(v1,v2) or false + end + end +end + +--- filter a sequence using a predicate function +-- @param iter a sequence of one or two values +-- @param pred a boolean function; may take two arguments +-- @param arg optional argument to pass to function. +function seq.filter (iter,pred,arg) + pred = function_arg(2,pred) + return function () + local v1,v2 + while true do + v1,v2 = iter() + if v1 == nil then return nil end + if arg then + if pred(v1,arg) then return v1,v2 end + else + if pred(v1,v2) then return v1,v2 end + end + end + end +end + +--- 'reduce' a sequence using a binary function. +-- @param fun a function of two arguments +-- @param iter a sequence +-- @param oldval optional initial value +-- @usage seq.reduce(operator.add,seq.list{1,2,3,4}) == 10 +-- @usage seq.reduce('-',{1,2,3,4,5}) == -13 +function seq.reduce (fun,iter,oldval) + fun = function_arg(1,fun) + iter = default_iter(iter) + if not oldval then + oldval = iter() + end + local val = oldval + for v in iter do + val = fun(val,v) + end + return val +end + +--- take the first n values from the sequence. +-- @param iter a sequence of one or two values +-- @param n number of items to take +-- @return a sequence of at most n items +function seq.take (iter,n) + local i = 1 + iter = default_iter(iter) + return function() + if i > n then return end + local val1,val2 = iter() + if not val1 then return end + i = i + 1 + return val1,val2 + end +end + +--- skip the first n values of a sequence +-- @param iter a sequence of one or more values +-- @param n number of items to skip +function seq.skip (iter,n) + n = n or 1 + for i = 1,n do iter() end + return iter +end + +--- a sequence with a sequence count and the original value.
    +-- enum(copy(ls)) is a roundabout way of saying ipairs(ls). +-- @param iter a single or double valued sequence +-- @return sequence of (i,v), i = 1..n and v is from iter. +function seq.enum (iter) + local i = 0 + iter = default_iter(iter) + return function () + local val1,val2 = iter() + if not val1 then return end + i = i + 1 + return i,val1,val2 + end +end + +--- map using a named method over a sequence. +-- @param iter a sequence +-- @param name the method name +-- @param arg1 optional first extra argument +-- @param arg2 optional second extra argument +function seq.mapmethod (iter,name,arg1,arg2) + iter = default_iter(iter) + return function() + local val = iter() + if not val then return end + local fn = val[name] + if not fn then error(type(val).." does not have method "..name) end + return fn(val,arg1,arg2) + end +end + +--- a sequence of (last,current) values from another sequence. +-- This will return S(i-1),S(i) if given S(i) +-- @param iter a sequence +function seq.last (iter) + iter = default_iter(iter) + local l = iter() + if l == nil then return nil end + return function () + local val,ll + val = iter() + if val == nil then return nil end + ll = l + l = val + return val,ll + end +end + +--- call the function on each element of the sequence. +-- @param iter a sequence with up to 3 values +-- @param fn a function +function seq.foreach(iter,fn) + fn = function_arg(2,fn) + for i1,i2,i3 in default_iter(iter) do fn(i1,i2,i3) end +end + +---------------------- Sequence Adapters --------------------- + +local SMT +local callable = utils.is_callable + +local function SW (iter,...) + if callable(iter) then + return setmetatable({iter=iter},SMT) + else + return iter,... + end +end + + +-- can't directly look these up in seq because of the wrong argument order... +local map,reduce,mapmethod = seq.map, seq.reduce, seq.mapmethod +local overrides = { + map = function(self,fun,arg) + return map(fun,self,arg) + end, + reduce = function(self,fun) + return reduce(fun,self) + end +} + +SMT = { + __index = function (tbl,key) + local s = overrides[key] or seq[key] + if s then + return function(sw,...) return SW(s(sw.iter,...)) end + else + return function(sw,...) return SW(mapmethod(sw.iter,key,...)) end + end + end, + __call = function (sw) + return sw.iter() + end, +} + +setmetatable(seq,{ + __call = function(tbl,iter) + if not callable(iter) then + if type(iter) == 'table' then iter = seq.list(iter) + else return iter + end + end + return setmetatable({iter=iter},SMT) + end +}) + +--- create a wrapped iterator over all lines in the file. +-- @param f either a filename or nil (for standard input) +-- @return a sequence wrapper +function seq.lines (f) + local iter = f and io.lines(f) or io.lines() + return SW(iter) +end + +function seq.import () + _G.debug.setmetatable(function() end,{ + __index = function(tbl,key) + local s = overrides[key] or seq[key] + if s then return s + else + return function(s,...) return seq.mapmethod(s,key,...) end + end + end + }) +end + +return seq diff --git a/Utils/luarocks/share/lua/5.1/pl/sip.lua b/Utils/luarocks/share/lua/5.1/pl/sip.lua new file mode 100644 index 000000000..c739a54ca --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/sip.lua @@ -0,0 +1,335 @@ +--- Simple Input Patterns (SIP).

    +-- SIP patterns start with '$', then a +-- one-letter type, and then an optional variable in curly braces.

    +-- Example: +--

    +--  sip.match('$v=$q','name="dolly"',res)
    +--  ==> res=={'name','dolly'}
    +--  sip.match('($q{first},$q{second})','("john","smith")',res)
    +--  ==> res=={second='smith',first='john'}
    +-- 
    +--
    +-- Type names
    +-- v    identifier
    +-- i     integer
    +-- f     floating-point
    +-- q    quoted string
    +-- ([{<  match up to closing bracket
    +-- 
    +--

    +-- See the Guide +-- @class module +-- @name pl.sip + +local append,concat = table.insert,table.concat +local concat = table.concat +local ipairs,loadstring,type,unpack = ipairs,loadstring,type,unpack +local io,_G = io,_G +local print,rawget = print,rawget + +local patterns = { + FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', + INTEGER = '[+%-%d]%d*', + IDEN = '[%a_][%w_]*', + FILE = '[%a%.\\][:%][%w%._%-\\]*' +} + +local function assert_arg(idx,val,tp) + if type(val) ~= tp then + error("argument "..idx.." must be "..tp, 2) + end +end + + +--[[ +module ('pl.sip',utils._module) +]] + +local sip = {} + +local brackets = {['<'] = '>', ['('] = ')', ['{'] = '}', ['['] = ']' } +local stdclasses = {a=1,c=0,d=1,l=1,p=0,u=1,w=1,x=1,s=0} + +local _patterns = {} + + +local function group(s) + return '('..s..')' +end + +-- escape all magic characters except $, which has special meaning +-- Also, un-escape any characters after $, so $( passes through as is. +local function escape (spec) + --_G.print('spec',spec) + local res = spec:gsub('[%-%.%+%[%]%(%)%^%%%?%*]','%%%1'):gsub('%$%%(%S)','$%1') + --_G.print('res',res) + return res +end + +local function imcompressible (s) + return s:gsub('%s+','\001') +end + +-- [handling of spaces in patterns] +-- spaces may be 'compressed' (i.e will match zero or more spaces) +-- unless this occurs within a number or an identifier. So we mark +-- the four possible imcompressible patterns first and then replace. +-- The possible alnum patterns are v,f,a,d,x,l and u. +local function compress_spaces (s) + s = s:gsub('%$[vifadxlu]%s+%$[vfadxlu]',imcompressible) + s = s:gsub('[%w_]%s+[%w_]',imcompressible) + s = s:gsub('[%w_]%s+%$[vfadxlu]',imcompressible) + s = s:gsub('%$[vfadxlu]%s+[%w_]',imcompressible) + s = s:gsub('%s+','%%s*') + s = s:gsub('\001',' ') + return s +end + +--- convert a SIP pattern into the equivalent Lua string pattern. +-- @param spec a SIP pattern +-- @param options a table; only the at_start field is +-- currently meaningful and esures that the pattern is anchored +-- at the start of the string. +-- @return a Lua string pattern. +function sip.create_pattern (spec,options) + assert_arg(1,spec,'string') + local fieldnames,fieldtypes = {},{} + + if type(spec) == 'string' then + spec = escape(spec) + else + local res = {} + for i,s in ipairs(spec) do + res[i] = escape(s) + end + spec = concat(res,'.-') + end + + local kount = 1 + + local function addfield (name,type) + if not name then name = kount end + if fieldnames then append(fieldnames,name) end + if fieldtypes then fieldtypes[name] = type end + kount = kount + 1 + end + + local named_vars, pattern + named_vars = spec:find('{%a+}') + pattern = '%$%S' + + if options and options.at_start then + spec = '^'..spec + end + if spec:sub(-1,-1) == '$' then + spec = spec:sub(1,-2)..'$r' + if named_vars then spec = spec..'{rest}' end + end + + + local names + + if named_vars then + names = {} + spec = spec:gsub('{(%a+)}',function(name) + append(names,name) + return '' + end) + end + spec = compress_spaces(spec) + + local k = 1 + local err + local r = (spec:gsub(pattern,function(s) + local type,name + type = s:sub(2,2) + if names then name = names[k]; k=k+1 end + -- this kludge is necessary because %q generates two matches, and + -- we want to ignore the first. Not a problem for named captures. + if not names and type == 'q' then + addfield(nil,'Q') + else + addfield(name,type) + end + local res + if type == 'v' then + res = group(patterns.IDEN) + elseif type == 'i' then + res = group(patterns.INTEGER) + elseif type == 'f' then + res = group(patterns.FLOAT) + elseif type == 'r' then + res = '(%S.*)' + elseif type == 'q' then + -- some Lua pattern matching voodoo; we want to match '...' as + -- well as "...", and can use the fact that %n will match a + -- previous capture. Adding the extra field above comes from needing + -- to accomodate the extra spurious match (which is either ' or ") + addfield(name,type) + res = '(["\'])(.-)%'..(kount-2) + elseif type == 'p' then + res = '([%a]?[:]?[\\/%.%w_]+)' + else + local endbracket = brackets[type] + if endbracket then + res = '(%b'..type..endbracket..')' + elseif stdclasses[type] or stdclasses[type:lower()] then + res = '(%'..type..'+)' + else + err = "unknown format type or character class" + end + end + return res + end)) + --print(r,err) + if err then + return nil,err + else + return r,fieldnames,fieldtypes + end +end + + +local function tnumber (s) + return s == 'd' or s == 'i' or s == 'f' +end + +function sip.create_spec_fun(spec,options) + local fieldtypes,fieldnames + local ls = {} + spec,fieldnames,fieldtypes = sip.create_pattern(spec,options) + if not spec then return spec,fieldnames end + local named_vars = type(fieldnames[1]) == 'string' + for i = 1,#fieldnames do + append(ls,'mm'..i) + end + local fun = ('return (function(s,res)\n\tlocal %s = s:match(%q)\n'):format(concat(ls,','),spec) + fun = fun..'\tif not mm1 then return false end\n' + local k=1 + for i,f in ipairs(fieldnames) do + if f ~= '_' then + local var = 'mm'..i + if tnumber(fieldtypes[f]) then + var = 'tonumber('..var..')' + elseif brackets[fieldtypes[f]] then + var = var..':sub(2,-2)' + end + if named_vars then + fun = ('%s\tres.%s = %s\n'):format(fun,f,var) + else + if fieldtypes[f] ~= 'Q' then -- we skip the string-delim capture + fun = ('%s\tres[%d] = %s\n'):format(fun,k,var) + k = k + 1 + end + end + end + end + return fun..'\treturn true\nend)\n', named_vars +end + +--- convert a SIP pattern into a matching function. +-- The returned function takes two arguments, the line and an empty table. +-- If the line matched the pattern, then this function return true +-- and the table is filled with field-value pairs. +-- @param spec a SIP pattern +-- @param options optional table; {anywhere=true} will stop pattern anchoring at start +-- @return a function if successful, or nil, +function sip.compile(spec,options) + assert_arg(1,spec,'string') + local fun,names = sip.create_spec_fun(spec,options) + if not fun then return nil,names end + if rawget(_G,'_DEBUG') then print(fun) end + local chunk,err = loadstring(fun,'tmp') + if err then return nil,err end + return chunk(),names +end + +local cache = {} + +--- match a SIP pattern against a string. +-- @param spec a SIP pattern +-- @param line a string +-- @param res a table to receive values +-- @param options (optional) option table +-- @return true or false +function sip.match (spec,line,res,options) + assert_arg(1,spec,'string') + assert_arg(2,line,'string') + assert_arg(3,res,'table') + if not cache[spec] then + cache[spec] = sip.compile(spec,options) + end + return cache[spec](line,res) +end + +--- match a SIP pattern against the start of a string. +-- @param spec a SIP pattern +-- @param line a string +-- @param res a table to receive values +-- @return true or false +function sip.match_at_start (spec,line,res) + return sip.match(spec,line,res,{at_start=true}) +end + +--- given a pattern and a file object, return an iterator over the results +-- @param spec a SIP pattern +-- @param f a file - use standard input if not specified. +function sip.fields (spec,f) + assert_arg(1,spec,'string') + f = f or io.stdin + local fun,err = sip.compile(spec) + if not fun then return nil,err end + local res = {} + return function() + while true do + local line = f:read() + if not line then return end + if fun(line,res) then + local values = res + res = {} + return unpack(values) + end + end + end +end + +--- register a match which will be used in the read function. +-- @param spec a SIP pattern +-- @param fun a function to be called with the results of the match +-- @see read +function sip.pattern (spec,fun) + assert_arg(1,spec,'string') + local pat,named = sip.compile(spec) + append(_patterns,{pat=pat,named=named,callback=fun or false}) +end + +--- enter a loop which applies all registered matches to the input file. +-- @param f a file object; if nil, then io.stdin is assumed. +function sip.read (f) + local owned,err + f = f or io.stdin + if type(f) == 'string' then + f,err = io.open(f) + if not f then return nil,err end + owned = true + end + local res = {} + for line in f:lines() do + for _,item in ipairs(_patterns) do + if item.pat(line,res) then + if item.callback then + if item.named then + item.callback(res) + else + item.callback(unpack(res)) + end + end + res = {} + break + end + end + end + if owned then f:close() end +end + +return sip diff --git a/Utils/luarocks/share/lua/5.1/pl/strict.lua b/Utils/luarocks/share/lua/5.1/pl/strict.lua new file mode 100644 index 000000000..f7d8c94c8 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/strict.lua @@ -0,0 +1,71 @@ +--- Checks uses of undeclared global variables. +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +-- @class module +-- @name pl.strict + +require 'debug' +local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget +local handler,hooked + +local mt = getmetatable(_G) +if mt == nil then + mt = {} + setmetatable(_G, mt) +elseif mt.hook then + hooked = true +end + +-- predeclaring _PROMPT keeps the Lua Interpreter happy +mt.__declared = {_PROMPT=true} + +local function what () + local d = getinfo(3, "S") + return d and d.what or "C" +end + +mt.__newindex = function (t, n, v) + if not mt.__declared[n] then + local w = what() + if w ~= "main" and w ~= "C" then + error("assign to undeclared variable '"..n.."'", 2) + end + mt.__declared[n] = true + end + rawset(t, n, v) +end + +handler = function(t,n) + if not mt.__declared[n] and what() ~= "C" then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) +end + +function package.strict (mod) + local mt = getmetatable(mod) + if mt == nil then + mt = {} + setmetatable(mod, mt) + end + mt.__declared = {} + mt.__newindex = function(t, n, v) + mt.__declared[n] = true + rawset(t, n, v) + end + mt.__index = function(t,n) + if not mt.__declared[n] then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) + end +end + +if not hooked then + mt.__index = handler +else + mt.hook(handler) +end + + diff --git a/Utils/luarocks/share/lua/5.1/pl/stringio.lua b/Utils/luarocks/share/lua/5.1/pl/stringio.lua new file mode 100644 index 000000000..0129ddd56 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/stringio.lua @@ -0,0 +1,144 @@ +--- Reading and writing strings using file-like objects.
    +--

    +--  f = stringio.open(text)
    +--  l1 = f:read()  -- read first line
    +--  n,m = f:read ('*n','*n') -- read two numbers
    +--  for line in f:lines() do print(line) end -- iterate over all lines
    +--  f = stringio.create()
    +--  f:write('hello')
    +--  f:write('dolly')
    +--  assert(f:value(),'hellodolly')
    +-- 
    +-- See the Guide. +-- @class module +-- @name pl.stringio + +local getmetatable,tostring,unpack,tonumber = getmetatable,tostring,unpack,tonumber +local concat,append = table.concat,table.insert + +local stringio = {} + +--- Writer class +local SW = {} +SW.__index = SW + +local function xwrite(self,...) + local args = {...} --arguments may not be nil! + for i = 1, #args do + append(self.tbl,args[i]) + end +end + +function SW:write(arg1,arg2,...) + if arg2 then + xwrite(self,arg1,arg2,...) + else + append(self.tbl,arg1) + end +end + +function SW:writef(fmt,...) + self:write(fmt:format(...)) +end + +function SW:value() + return concat(self.tbl) +end + +function SW:close() -- for compatibility only +end + +function SW:seek() +end + +--- Reader class +local SR = {} +SR.__index = SR + +function SR:_read(fmt) + local i,str = self.i,self.str + local sz = #str + if i >= sz then return nil end + local res + if fmt == nil or fmt == '*l' then + local idx = str:find('\n',i) or (sz+1) + res = str:sub(i,idx-1) + self.i = idx+1 + elseif fmt == '*a' then + res = str:sub(i) + self.i = sz + elseif fmt == '*n' then + local _,i2,i2,idx + _,idx = str:find ('%s*%d+',i) + _,i2 = str:find ('%.%d+',idx+1) + if i2 then idx = i2 end + _,i2 = str:find ('[eE][%+%-]*%d+',idx+1) + if i2 then idx = i2 end + local val = str:sub(i,idx) + res = tonumber(val) + self.i = idx+1 + elseif type(fmt) == 'number' then + res = str:sub(i,i+fmt-1) + self.i = i + fmt + else + error("bad read format",2) + end + return res +end + +function SR:read(...) + local fmts = {...} + if #fmts <= 1 then + return self:_read(fmts[1]) + else + local res = {} + for i = 1, #fmts do + res[i] = self:_read(fmts[i]) + end + return unpack(res) + end +end + +function SR:seek(whence,offset) + local base + whence = whence or 'cur' + offset = offset or 0 + if whence == 'set' then + base = 1 + elseif whence == 'cur' then + base = self.i + elseif whence == 'end' then + base = #self.str + end + self.i = base + offset + return self.i +end + +function SR:lines() + return function() + return self:read() + end +end + +function SR:close() -- for compatibility only +end + +--- create a file-like object which can be used to construct a string. +-- The resulting object has an extra value() method for +-- retrieving the string value. +-- @usage f = create(); f:write('hello, dolly\n'); print(f:value()) +function stringio.create() + return setmetatable({tbl={}},SW) +end + +--- create a file-like object for reading from a given string. +-- @param s The input string. +function stringio.open(s) + return setmetatable({str=s,i=1},SR) +end + +function stringio.lines(s) + return stringio.open(s):lines() +end + +return stringio diff --git a/Utils/luarocks/share/lua/5.1/pl/stringx.lua b/Utils/luarocks/share/lua/5.1/pl/stringx.lua new file mode 100644 index 000000000..5699b2039 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/stringx.lua @@ -0,0 +1,441 @@ +--- Python-style string library.

    +-- see 3.6.1 of the Python reference.

    +-- If you want to make these available as string methods, then say +-- stringx.import() to bring them into the standard string +-- table. +-- @class module +-- @name pl.stringx +local string = string +local find = string.find +local type,setmetatable,getmetatable,ipairs,unpack = type,setmetatable,getmetatable,ipairs,unpack +local error,tostring = error,tostring +local gsub = string.gsub +local rep = string.rep +local sub = string.sub +local concat = table.concat +local utils = require 'pl.utils' +local escape = utils.escape +local ceil = math.ceil +local _G = _G +local assert_arg,usplit,list_MT = utils.assert_arg,utils.split,utils.stdmt.List +local lstrip + +local function assert_string (n,s) + assert_arg(n,s,'string') +end + +local function non_empty(s) + return #s > 0 +end + +local function assert_nonempty_string(n,s) + assert_arg(n,s,'string',non_empty,'must be a non-empty string') +end + +--[[ +module ('pl.stringx',utils._module) +]] + +local stringx = {} + +--- does s only contain alphabetic characters?. +-- @param s a string +function stringx.isalpha(s) + assert_string(1,s) + return find(s,'^%a+$') == 1 +end + +--- does s only contain digits?. +-- @param s a string +function stringx.isdigit(s) + assert_string(1,s) + return find(s,'^%d+$') == 1 +end + +--- does s only contain alphanumeric characters?. +-- @param s a string +function stringx.isalnum(s) + assert_string(1,s) + return find(s,'^%w+$') == 1 +end + +--- does s only contain spaces?. +-- @param s a string +function stringx.isspace(s) + assert_string(1,s) + return find(s,'^%s+$') == 1 +end + +--- does s only contain lower case characters?. +-- @param s a string +function stringx.islower(s) + assert_string(1,s) + return find(s,'^[%l%s]+$') == 1 +end + +--- does s only contain upper case characters?. +-- @param s a string +function stringx.isupper(s) + assert_string(1,s) + return find(s,'^[%u%s]+$') == 1 +end + +--- concatenate the strings using this string as a delimiter. +-- @param self the string +-- @param seq a table of strings or numbers +-- @usage (' '):join {1,2,3} == '1 2 3' +function stringx.join (self,seq) + assert_string(1,self) + return concat(seq,self) +end + +--- does string start with the substring?. +-- @param self the string +-- @param s2 a string +function stringx.startswith(self,s2) + assert_string(1,self) + assert_string(2,s2) + return find(self,s2,1,true) == 1 +end + +local function _find_all(s,sub,first,last) + if sub == '' then return #s+1,#s end + local i1,i2 = find(s,sub,first,true) + local res + local k = 0 + while i1 do + res = i1 + k = k + 1 + i1,i2 = find(s,sub,i2+1,true) + if last and i1 > last then break end + end + return res,k +end + +--- does string end with the given substring?. +-- @param s a string +-- @param send a substring or a table of suffixes +function stringx.endswith(s,send) + assert_string(1,s) + if type(send) == 'string' then + return #s >= #send and s:find(send, #s-#send+1, true) and true or false + elseif type(send) == 'table' then + local endswith = stringx.endswith + for _,suffix in ipairs(send) do + if endswith(s,suffix) then return true end + end + return false + else + error('argument #2: either a substring or a table of suffixes expected') + end +end + +-- break string into a list of lines +-- @param self the string +-- @param keepends (currently not used) +function stringx.splitlines (self,keepends) + assert_string(1,self) + local res = usplit(self,'[\r\n]') + -- we are currently hacking around a problem with utils.split (see stringx.split) + if #res == 0 then res = {''} end + return setmetatable(res,list_MT) +end + +local function tab_expand (self,n) + return (gsub(self,'([^\t]*)\t', function(s) + return s..(' '):rep(n - #s % n) + end)) +end + +--- replace all tabs in s with n spaces. If not specified, n defaults to 8. +-- with 0.9.5 this now correctly expands to the next tab stop (if you really +-- want to just replace tabs, use :gsub('\t',' ') etc) +-- @param self the string +-- @param n number of spaces to expand each tab, (default 8) +function stringx.expandtabs(self,n) + assert_string(1,self) + n = n or 8 + if not self:find '\n' then return tab_expand(self,n) end + local res,i = {},1 + for line in stringx.lines(self) do + res[i] = tab_expand(line,n) + i = i + 1 + end + return table.concat(res,'\n') +end + +--- find index of first instance of sub in s from the left. +-- @param self the string +-- @param sub substring +-- @param i1 start index +function stringx.lfind(self,sub,i1) + assert_string(1,self) + assert_string(2,sub) + local idx = find(self,sub,i1,true) + if idx then return idx else return nil end +end + +--- find index of first instance of sub in s from the right. +-- @param self the string +-- @param sub substring +-- @param first first index +-- @param last last index +function stringx.rfind(self,sub,first,last) + assert_string(1,self) + assert_string(2,sub) + local idx = _find_all(self,sub,first,last) + if idx then return idx else return nil end +end + +--- replace up to n instances of old by new in the string s. +-- if n is not present, replace all instances. +-- @param s the string +-- @param old the target substring +-- @param new the substitution +-- @param n optional maximum number of substitutions +-- @return result string +-- @return the number of substitutions +function stringx.replace(s,old,new,n) + assert_string(1,s) + assert_string(1,old) + return (gsub(s,escape(old),new:gsub('%%','%%%%'),n)) +end + +--- split a string into a list of strings using a delimiter. +-- @class function +-- @name split +-- @param self the string +-- @param re a delimiter (defaults to whitespace) +-- @param n maximum number of results +-- @usage #(('one two'):split()) == 2 +-- @usage ('one,two,three'):split(',') == List{'one','two','three'} +-- @usage ('one,two,three'):split(',',2) == List{'one','two,three'} +function stringx.split(self,re,n) + local s = self + local plain = true + if not re then -- default spaces + s = lstrip(s) + plain = false + end + local res = usplit(s,re,plain,n) + if re and re ~= '' and find(s,re,-#re,true) then + res[#res+1] = "" + end + return setmetatable(res,list_MT) +end + +--- split a string using a pattern. Note that at least one value will be returned! +-- @param self the string +-- @param re a Lua string pattern (defaults to whitespace) +-- @return the parts of the string +-- @usage a,b = line:splitv('=') +function stringx.splitv (self,re) + assert_string(1,self) + return utils.splitv(self,re) +end + +local function copy(self) + return self..'' +end + +--- count all instances of substring in string. +-- @param self the string +-- @param sub substring +function stringx.count(self,sub) + assert_string(1,self) + local i,k = _find_all(self,sub,1) + return k +end + +local function _just(s,w,ch,left,right) + local n = #s + if w > n then + if not ch then ch = ' ' end + local f1,f2 + if left and right then + local ln = ceil((w-n)/2) + local rn = w - n - ln + f1 = rep(ch,ln) + f2 = rep(ch,rn) + elseif right then + f1 = rep(ch,w-n) + f2 = '' + else + f2 = rep(ch,w-n) + f1 = '' + end + return f1..s..f2 + else + return copy(s) + end +end + +--- left-justify s with width w. +-- @param self the string +-- @param w width of justification +-- @param ch padding character, default ' ' +function stringx.ljust(self,w,ch) + assert_string(1,self) + assert_arg(2,w,'number') + return _just(self,w,ch,true,false) +end + +--- right-justify s with width w. +-- @param s the string +-- @param w width of justification +-- @param ch padding character, default ' ' +function stringx.rjust(s,w,ch) + assert_string(1,s) + assert_arg(2,w,'number') + return _just(s,w,ch,false,true) +end + +--- center-justify s with width w. +-- @param s the string +-- @param w width of justification +-- @param ch padding character, default ' ' +function stringx.center(s,w,ch) + assert_string(1,s) + assert_arg(2,w,'number') + return _just(s,w,ch,true,true) +end + +local function _strip(s,left,right,chrs) + if not chrs then + chrs = '%s' + else + chrs = '['..escape(chrs)..']' + end + if left then + local i1,i2 = find(s,'^'..chrs..'*') + if i2 >= i1 then + s = sub(s,i2+1) + end + end + if right then + local i1,i2 = find(s,chrs..'*$') + if i2 >= i1 then + s = sub(s,1,i1-1) + end + end + return s +end + +--- trim any whitespace on the left of s. +-- @param self the string +-- @param chrs default space, can be a string of characters to be trimmed +function stringx.lstrip(self,chrs) + assert_string(1,self) + return _strip(self,true,false,chrs) +end +lstrip = stringx.lstrip + +--- trim any whitespace on the right of s. +-- @param s the string +-- @param chrs default space, can be a string of characters to be trimmed +function stringx.rstrip(s,chrs) + assert_string(1,s) + return _strip(s,false,true,chrs) +end + +--- trim any whitespace on both left and right of s. +-- @param self the string +-- @param chrs default space, can be a string of characters to be trimmed +function stringx.strip(self,chrs) + assert_string(1,self) + return _strip(self,true,true,chrs) +end + +-- The partition functions split a string using a delimiter into three parts: +-- the part before, the delimiter itself, and the part afterwards +local function _partition(p,delim,fn) + local i1,i2 = fn(p,delim) + if not i1 or i1 == -1 then + return p,'','' + else + if not i2 then i2 = i1 end + return sub(p,1,i1-1),sub(p,i1,i2),sub(p,i2+1) + end +end + +--- partition the string using first occurance of a delimiter +-- @param self the string +-- @param ch delimiter +-- @return part before ch +-- @return ch +-- @return part after ch +function stringx.partition(self,ch) + assert_string(1,self) + assert_nonempty_string(2,ch) + return _partition(self,ch,stringx.lfind) +end + +--- partition the string p using last occurance of a delimiter +-- @param self the string +-- @param ch delimiter +-- @return part before ch +-- @return ch +-- @return part after ch +function stringx.rpartition(self,ch) + assert_string(1,self) + assert_nonempty_string(2,ch) + return _partition(self,ch,stringx.rfind) +end + +--- return the 'character' at the index. +-- @param self the string +-- @param idx an index (can be negative) +-- @return a substring of length 1 if successful, empty string otherwise. +function stringx.at(self,idx) + assert_string(1,self) + assert_arg(2,idx,'number') + return sub(self,idx,idx) +end + +--- return an interator over all lines in a string +-- @param self the string +-- @return an iterator +function stringx.lines (self) + assert_string(1,self) + local s = self + if not s:find '\n$' then s = s..'\n' end + return s:gmatch('([^\n]*)\n') +end + +--- iniital word letters uppercase ('title case'). +-- Here 'words' mean chunks of non-space characters. +-- @param self the string +-- @return a string with each word's first letter uppercase +function stringx.title(self) + return (self:gsub('(%S)(%S*)',function(f,r) + return f:upper()..r:lower() + end)) +end + +stringx.capitalize = stringx.title + +local elipsis = '...' +local n_elipsis = #elipsis + +--- return a shorted version of a string. +-- @param self the string +-- @param sz the maxinum size allowed +-- @param tail true if we want to show the end of the string (head otherwise) +function stringx.shorten(self,sz,tail) + if #self > sz then + if sz < n_elipsis then return elipsis:sub(1,sz) end + if tail then + local i = #self - sz + 1 + n_elipsis + return elipsis .. self:sub(i) + else + return self:sub(1,sz-n_elipsis) .. elipsis + end + end + return self +end + +function stringx.import(dont_overload) + utils.import(stringx,string) +end + +return stringx diff --git a/Utils/luarocks/share/lua/5.1/pl/tablex.lua b/Utils/luarocks/share/lua/5.1/pl/tablex.lua new file mode 100644 index 000000000..bfddb4b70 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/tablex.lua @@ -0,0 +1,766 @@ +--- Extended operations on Lua tables. +-- @class module +-- @name pl.tablex +local getmetatable,setmetatable,require = getmetatable,setmetatable,require +local append,remove = table.insert,table.remove +local min,max = math.min,math.max +local pairs,type,unpack,next,ipairs,select,tostring = pairs,type,unpack,next,ipairs,select,tostring +local utils = require ('pl.utils') +local function_arg = utils.function_arg +local Set = utils.stdmt.Set +local List = utils.stdmt.List +local Map = utils.stdmt.Map +local assert_arg = utils.assert_arg + +--[[ +module ('pl.tablex',utils._module) +]] + +local tablex = {} + +-- generally, functions that make copies of tables try to preserve the metatable. +-- However, when the source has no obvious type, then we attach appropriate metatables +-- like List, Map, etc to the result. +local function setmeta (res,tbl,def) + return setmetatable(res,getmetatable(tbl) or def) +end + +local function makelist (res) + return setmetatable(res,List) +end + +--- copy a table into another, in-place. +-- @param t1 destination table +-- @param t2 source table +-- @return first table +function tablex.update (t1,t2) + assert_arg(1,t1,'table') + assert_arg(2,t2,'table') + for k,v in pairs(t2) do + t1[k] = v + end + return t1 +end + +--- total number of elements in this table.
    +-- Note that this is distinct from #t, which is the number +-- of values in the array part; this value will always +-- be greater or equal. The difference gives the size of +-- the hash part, for practical purposes. +-- @param t a table +-- @return the size +function tablex.size (t) + assert_arg(1,t,'table') + local i = 0 + for k in pairs(t) do i = i + 1 end + return i +end + +--- make a shallow copy of a table +-- @param t source table +-- @return new table +function tablex.copy (t) + assert_arg(1,t,'table') + local res = {} + for k,v in pairs(t) do + res[k] = v + end + return res +end + +--- make a deep copy of a table, recursively copying all the keys and fields. +-- This will also set the copied table's metatable to that of the original. +-- @param t A table +-- @return new table +function tablex.deepcopy(t) + assert_arg(1,t,'table') + if type(t) ~= 'table' then return t end + local mt = getmetatable(t) + local res = {} + for k,v in pairs(t) do + if type(v) == 'table' then + v = tablex.deepcopy(v) + end + res[k] = v + end + setmetatable(res,mt) + return res +end + +local abs = math.abs + +--- compare two values. +-- if they are tables, then compare their keys and fields recursively. +-- @param t1 A value +-- @param t2 A value +-- @param ignore_mt if true, ignore __eq metamethod (default false) +-- @param eps if defined, then used for any number comparisons +-- @return true or false +function tablex.deepcompare(t1,t2,ignore_mt,eps) + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false end + -- non-table types can be directly compared + if ty1 ~= 'table' then + if ty1 == 'number' and eps then return abs(t1-t2) < eps end + return t1 == t2 + end + -- as well as tables which have the metamethod __eq + local mt = getmetatable(t1) + if not ignore_mt and mt and mt.__eq then return t1 == t2 end + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not tablex.deepcompare(v1,v2,ignore_mt,eps) then return false end + end + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if v1 == nil or not tablex.deepcompare(v1,v2,ignore_mt,eps) then return false end + end + return true +end + +--- compare two list-like tables using a predicate. +-- @param t1 a table +-- @param t2 a table +-- @param cmp A comparison function +function tablex.compare (t1,t2,cmp) + assert_arg(1,t1,'table') + assert_arg(2,t2,'table') + if #t1 ~= #t2 then return false end + cmp = function_arg(3,cmp) + for k in ipairs(t1) do + if not cmp(t1[k],t2[k]) then return false end + end + return true +end + +--- compare two list-like tables using an optional predicate, without regard for element order. +-- @param t1 a list-like table +-- @param t2 a list-like table +-- @param cmp A comparison function (may be nil) +function tablex.compare_no_order (t1,t2,cmp) + assert_arg(1,t1,'table') + assert_arg(2,t2,'table') + if cmp then cmp = function_arg(3,cmp) end + if #t1 ~= #t2 then return false end + local visited = {} + for i = 1,#t1 do + local val = t1[i] + local gotcha + for j = 1,#t2 do if not visited[j] then + local match + if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end + if match then + gotcha = j + break + end + end end + if not gotcha then return false end + visited[gotcha] = true + end + return true +end + + +--- return the index of a value in a list. +-- Like string.find, there is an optional index to start searching, +-- which can be negative. +-- @param t A list-like table (i.e. with numerical indices) +-- @param val A value +-- @param idx index to start; -1 means last element,etc (default 1) +-- @return index of value or nil if not found +-- @usage find({10,20,30},20) == 2 +-- @usage find({'a','b','a','c'},'a',2) == 3 + +function tablex.find(t,val,idx) + assert_arg(1,t,'table') + idx = idx or 1 + if idx < 0 then idx = #t + idx + 1 end + for i = idx,#t do + if t[i] == val then return i end + end + return nil +end + +--- return the index of a value in a list, searching from the end. +-- Like string.find, there is an optional index to start searching, +-- which can be negative. +-- @param t A list-like table (i.e. with numerical indices) +-- @param val A value +-- @param idx index to start; -1 means last element,etc (default 1) +-- @return index of value or nil if not found +-- @usage rfind({10,10,10},10) == 3 +function tablex.rfind(t,val,idx) + assert_arg(1,t,'table') + idx = idx or #t + if idx < 0 then idx = #t + idx + 1 end + for i = idx,1,-1 do + if t[i] == val then return i end + end + return nil +end + + +--- return the index (or key) of a value in a table using a comparison function. +-- @param t A table +-- @param cmp A comparison function +-- @param arg an optional second argument to the function +-- @return index of value, or nil if not found +-- @return value returned by comparison function +function tablex.find_if(t,cmp,arg) + assert_arg(1,t,'table') + cmp = function_arg(2,cmp) + for k,v in pairs(t) do + local c = cmp(v,arg) + if c then return k,c end + end + return nil +end + +--- return a list of all values in a table indexed by another list. +-- @param tbl a table +-- @param idx an index table (a list of keys) +-- @return a list-like table +-- @usage index_by({10,20,30,40},{2,4}) == {20,40} +-- @usage index_by({one=1,two=2,three=3},{'one','three'}) == {1,3} +function tablex.index_by(tbl,idx) + assert_arg(1,tbl,'table') + assert_arg(2,idx,'table') + local res = {} + for _,i in ipairs(idx) do + append(res,tbl[i]) + end + return setmeta(res,tbl,List) +end + +--- apply a function to all values of a table. +-- This returns a table of the results. +-- Any extra arguments are passed to the function. +-- @param fun A function that takes at least one argument +-- @param t A table +-- @param ... optional arguments +-- @usage map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4} +function tablex.map(fun,t,...) + assert_arg(1,t,'table') + fun = function_arg(1,fun) + local res = {} + for k,v in pairs(t) do + res[k] = fun(v,...) + end + return setmeta(res,t) +end + +--- apply a function to all values of a list. +-- This returns a table of the results. +-- Any extra arguments are passed to the function. +-- @param fun A function that takes at least one argument +-- @param t a table (applies to array part) +-- @param ... optional arguments +-- @return a list-like table +-- @usage imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900} +function tablex.imap(fun,t,...) + assert_arg(1,t,'table') + fun = function_arg(1,fun) + local res = {} + for i = 1,#t do + res[i] = fun(t[i],...) or false + end + return setmeta(res,t,List) +end + +--- apply a named method to values from a table. +-- @param name the method name +-- @param t a list-like table +-- @param ... any extra arguments to the method +function tablex.map_named_method (name,t,...) + assert_arg(1,name,'string') + assert_arg(2,t,'table') + local res = {} + for i = 1,#t do + local val = t[i] + local fun = val[name] + res[i] = fun(val,...) + end + return setmeta(res,t,List) +end + + +--- apply a function to all values of a table, in-place. +-- Any extra arguments are passed to the function. +-- @param fun A function that takes at least one argument +-- @param t a table +-- @param ... extra arguments +function tablex.transform (fun,t,...) + assert_arg(1,t,'table') + fun = function_arg(1,fun) + for k,v in pairs(t) do + t[v] = fun(v,...) + end +end + +--- generate a table of all numbers in a range +-- @param start number +-- @param finish number +-- @param step optional increment (default 1 for increasing, -1 for decreasing) +function tablex.range (start,finish,step) + local res = {} + local k = 1 + if not step then + if finish > start then step = finish > start and 1 or -1 end + end + for i=start,finish,step do res[k]=i; k=k+1 end + return res +end + +--- apply a function to values from two tables. +-- @param fun a function of at least two arguments +-- @param t1 a table +-- @param t2 a table +-- @param ... extra arguments +-- @return a table +-- @usage map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44} +function tablex.map2 (fun,t1,t2,...) + assert_arg(1,t1,'table') + assert_arg(2,t2,'table') + fun = function_arg(1,fun) + local res = {} + for k,v in pairs(t1) do + res[k] = fun(v,t2[k],...) + end + return setmeta(res,t1,List) +end + +--- apply a function to values from two arrays. +-- @param fun a function of at least two arguments +-- @param t1 a list-like table +-- @param t2 a list-like table +-- @param ... extra arguments +-- @usage imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23} +function tablex.imap2 (fun,t1,t2,...) + assert_arg(2,t1,'table') + assert_arg(3,t2,'table') + fun = function_arg(1,fun) + local res = {} + for i = 1,#t1 do + res[i] = fun(t1[i],t2[i],...) + end + return res +end + +--- 'reduce' a list using a binary function. +-- @param fun a function of two arguments +-- @param t a list-like table +-- @return the result of the function +-- @usage reduce('+',{1,2,3,4}) == 10 +function tablex.reduce (fun,t) + assert_arg(2,t,'table') + fun = function_arg(1,fun) + local n = #t + local res = t[1] + for i = 2,n do + res = fun(res,t[i]) + end + return res +end + +--- apply a function to all elements of a table. +-- The arguments to the function will be the value, +-- the key and finally any extra arguments passed to this function. +-- Note that the Lua 5.0 function table.foreach passed the key first. +-- @param t a table +-- @param fun a function with at least one argument +-- @param ... extra arguments +function tablex.foreach(t,fun,...) + assert_arg(1,t,'table') + fun = function_arg(2,fun) + for k,v in pairs(t) do + fun(v,k,...) + end +end + +--- apply a function to all elements of a list-like table in order. +-- The arguments to the function will be the value, +-- the index and finally any extra arguments passed to this function +-- @param t a table +-- @param fun a function with at least one argument +-- @param ... optional arguments +function tablex.foreachi(t,fun,...) + assert_arg(1,t,'table') + fun = function_arg(2,fun) + for k,v in ipairs(t) do + fun(v,k,...) + end +end + + +--- Apply a function to a number of tables. +-- A more general version of map +-- The result is a table containing the result of applying that function to the +-- ith value of each table. Length of output list is the minimum length of all the lists +-- @param fun a function of n arguments +-- @param ... n tables +-- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333} +-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300} +-- @param fun A function that takes as many arguments as there are tables +function tablex.mapn(fun,...) + fun = function_arg(1,fun) + local res = {} + local lists = {...} + local minn = 1e40 + for i = 1,#lists do + minn = min(minn,#(lists[i])) + end + for i = 1,minn do + local args = {} + for j = 1,#lists do + args[#args+1] = lists[j][i] + end + res[#res+1] = fun(unpack(args)) + end + return res +end + +--- call the function with the key and value pairs from a table. +-- The function can return a value and a key (note the order!). If both +-- are not nil, then this pair is inserted into the result. If only value is not nil, then +-- it is appended to the result. +-- @param fun A function which will be passed each key and value as arguments, plus any extra arguments to pairmap. +-- @param t A table +-- @param ... optional arguments +-- @usage pairmap({fred=10,bonzo=20},function(k,v) return v end) is {10,20} +-- @usage pairmap({one=1,two=2},function(k,v) return {k,v},k end) is {one={'one',1},two={'two',2}} +function tablex.pairmap(fun,t,...) + assert_arg(1,t,'table') + fun = function_arg(1,fun) + local res = {} + for k,v in pairs(t) do + local rv,rk = fun(k,v,...) + if rk then + res[rk] = rv + else + res[#res+1] = rv + end + end + return res +end + +local function keys_op(i,v) return i end + +--- return all the keys of a table in arbitrary order. +-- @param t A table +function tablex.keys(t) + assert_arg(1,t,'table') + return makelist(tablex.pairmap(keys_op,t)) +end + +local function values_op(i,v) return v end + +--- return all the values of the table in arbitrary order +-- @param t A table +function tablex.values(t) + assert_arg(1,t,'table') + return makelist(tablex.pairmap(values_op,t)) +end + +local function index_map_op (i,v) return i,v end + +--- create an index map from a list-like table. The original values become keys, +-- and the associated values are the indices into the original list. +-- @param t a list-like table +-- @return a map-like table +function tablex.index_map (t) + assert_arg(1,t,'table') + return setmetatable(tablex.pairmap(index_map_op,t),Map) +end + +local function set_op(i,v) return true,v end + +--- create a set from a list-like table. A set is a table where the original values +-- become keys, and the associated values are all true. +-- @param t a list-like table +-- @return a set (a map-like table) +function tablex.makeset (t) + assert_arg(1,t,'table') + return setmetatable(tablex.pairmap(set_op,t),Set) +end + + +--- combine two tables, either as union or intersection. Corresponds to +-- set operations for sets () but more general. Not particularly +-- useful for list-like tables. +-- @param t1 a table +-- @param t2 a table +-- @param dup true for a union, false for an intersection. +-- @usage merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34} +-- @usage merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23} +-- @see tablex.index_map +function tablex.merge (t1,t2,dup) + assert_arg(1,t1,'table') + assert_arg(2,t2,'table') + local res = {} + for k,v in pairs(t1) do + if dup or t2[k] then res[k] = v end + end + for k,v in pairs(t2) do + if dup or t1[k] then res[k] = v end + end + return setmeta(res,t1,Map) +end + +--- a new table which is the difference of two tables. +-- With sets (where the values are all true) this is set difference and +-- symmetric difference depending on the third parameter. +-- @param s1 a map-like table or set +-- @param s2 a map-like table or set +-- @param symm symmetric difference (default false) +-- @return a map-like table or set +function tablex.difference (s1,s2,symm) + assert_arg(1,s1,'table') + assert_arg(2,s2,'table') + local res = {} + for k,v in pairs(s1) do + if not s2[k] then res[k] = v end + end + if symm then + for k,v in pairs(s2) do + if not s1[k] then res[k] = v end + end + end + return setmeta(res,s1,Map) +end + +--- A table where the key/values are the values and value counts of the table. +-- @param t a list-like table +-- @param cmp a function that defines equality (otherwise uses ==) +-- @return a map-like table +-- @see seq.count_map +function tablex.count_map (t,cmp) + assert_arg(1,t,'table') + local res,mask = {},{} + cmp = function_arg(2,cmp) + local n = #t + for i,v in ipairs(t) do + if not mask[v] then + mask[v] = true + -- check this value against all other values + res[v] = 1 -- there's at least one instance + for j = i+1,n do + local w = t[j] + if cmp and cmp(v,w) or v == w then + res[v] = res[v] + 1 + mask[w] = true + end + end + end + end + return setmetatable(res,Map) +end + +--- filter a table's values using a predicate function +-- @param t a list-like table +-- @param pred a boolean function +-- @param arg optional argument to be passed as second argument of the predicate +function tablex.filter (t,pred,arg) + assert_arg(1,t,'table') + pred = function_arg(2,pred) + local res = {} + for k,v in ipairs(t) do + if pred(v,arg) then append(res,v) end + end + return setmeta(res,t,List) +end + +--- return a table where each element is a table of the ith values of an arbitrary +-- number of tables. It is equivalent to a matrix transpose. +-- @usage zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}} +function tablex.zip(...) + return tablex.mapn(function(...) return {...} end,...) +end + +local _copy +function _copy (dest,src,idest,isrc,nsrc,clean_tail) + idest = idest or 1 + isrc = isrc or 1 + local iend + if not nsrc then + nsrc = #src + iend = #src + else + iend = isrc + min(nsrc-1,#src-isrc) + end + if dest == src then -- special case + if idest > isrc and iend >= idest then -- overlapping ranges + src = tablex.sub(src,isrc,nsrc) + isrc = 1; iend = #src + end + end + for i = isrc,iend do + dest[idest] = src[i] + idest = idest + 1 + end + if clean_tail then + tablex.clear(dest,idest) + end + return dest +end + +--- copy an array into another one, resizing the destination if necessary.
    +-- @param dest a list-like table +-- @param src a list-like table +-- @param idest where to start copying values from source (default 1) +-- @param isrc where to start copying values into destination (default 1) +-- @param nsrc number of elements to copy from source (default source size) +function tablex.icopy (dest,src,idest,isrc,nsrc) + assert_arg(1,dest,'table') + assert_arg(2,src,'table') + return _copy(dest,src,idest,isrc,nsrc,true) +end + +--- copy an array into another one.
    +-- @param dest a list-like table +-- @param src a list-like table +-- @param idest where to start copying values from source (default 1) +-- @param isrc where to start copying values into destination (default 1) +-- @param nsrc number of elements to copy from source (default source size) +function tablex.move (dest,src,idest,isrc,nsrc) + assert_arg(1,dest,'table') + assert_arg(2,src,'table') + return _copy(dest,src,idest,isrc,nsrc,false) +end + +function tablex._normalize_slice(self,first,last) + local sz = #self + if not first then first=1 end + if first<0 then first=sz+first+1 end + -- make the range _inclusive_! + if not last then last=sz end + if last < 0 then last=sz+1+last end + return first,last +end + +--- Extract a range from a table, like 'string.sub'. +-- If first or last are negative then they are relative to the end of the list +-- eg. sub(t,-2) gives last 2 entries in a list, and +-- sub(t,-4,-2) gives from -4th to -2nd +-- @param t a list-like table +-- @param first An index +-- @param last An index +-- @return a new List +function tablex.sub(t,first,last) + assert_arg(1,t,'table') + first,last = tablex._normalize_slice(t,first,last) + local res={} + for i=first,last do append(res,t[i]) end + return setmeta(res,t,List) +end + +--- set an array range to a value. If it's a function we use the result +-- of applying it to the indices. +-- @param t a list-like table +-- @param val a value +-- @param i1 start range (default 1) +-- @param i2 end range (default table size) +function tablex.set (t,val,i1,i2) + i1,i2 = i1 or 1,i2 or #t + if utils.is_callable(val) then + for i = i1,i2 do + t[i] = val(i) + end + else + for i = i1,i2 do + t[i] = val + end + end +end + +--- create a new array of specified size with initial value. +-- @param n size +-- @param val initial value (can be nil, but don't expect # to work!) +-- @return the table +function tablex.new (n,val) + local res = {} + tablex.set(res,val,1,n) + return res +end + +--- clear out the contents of a table. +-- @param t a table +-- @param istart optional start position +function tablex.clear(t,istart) + istart = istart or 1 + for i = istart,#t do remove(t) end +end + +--- insert values into a table.
    +-- insertvalues(t, [pos,] values)
    +-- similar to table.insert but inserts values from given table "values", +-- not the object itself, into table "t" at position "pos". +function tablex.insertvalues(t, ...) + local pos, values + if select('#', ...) == 1 then + pos,values = #t+1, ... + else + pos,values = ... + end + if #values > 0 then + for i=#t,pos,-1 do + t[i+#values] = t[i] + end + local offset = 1 - pos + for i=pos,pos+#values-1 do + t[i] = values[i + offset] + end + end + return t +end + +--- remove a range of values from a table. +-- @param t a list-like table +-- @param i1 start index +-- @param i2 end index +-- @return the table +function tablex.removevalues (t,i1,i2) + i1,i2 = tablex._normalize_slice(t,i1,i2) + for i = i1,i2 do + remove(t,i1) + end + return t +end + +local _find +_find = function (t,value,tables) + for k,v in pairs(t) do + if v == value then return k end + end + for k,v in pairs(t) do + if not tables[v] and type(v) == 'table' then + tables[v] = true + local res = _find(v,value,tables) + if res then + res = tostring(res) + if type(k) ~= 'string' then + return '['..k..']'..res + else + return k..'.'..res + end + end + end + end +end + +--- find a value in a table by recursive search. +-- @param t the table +-- @param value the value +-- @param exclude any tables to avoid searching +-- @usage search(_G,math.sin,{package.path}) == 'math.sin' +-- @return a fieldspec, e.g. 'a.b' or 'math.sin' +function tablex.search (t,value,exclude) + assert_arg(1,t,'table') + local tables = {[t]=true} + if exclude then + for _,v in pairs(exclude) do tables[v] = true end + end + return _find(t,value,tables) +end + +return tablex diff --git a/Utils/luarocks/share/lua/5.1/pl/template.lua b/Utils/luarocks/share/lua/5.1/pl/template.lua new file mode 100644 index 000000000..dbff1f2fc --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/template.lua @@ -0,0 +1,99 @@ +--- A template preprocessor. +-- Originally by Ricki Lake +--

    There are two rules:

      +--
    • lines starting with # are Lua
    • +--
    • otherwise, `$(expr)` is the result of evaluating `expr`
    • +--
    +--
    +-- #  for i = 1,3 do
    +--    $(i) Hello, Word!
    +-- #  end
    +-- 
    +-- Other escape characters can be used, when the defaults conflict +-- with the output language. +--
    +-- > for _,n in pairs{'one','two','three'} do
    +--  static int l_${n} (luaState *state);
    +-- > end
    +-- 
    +-- See the Guide. +-- @class module +-- @name pl.template + +--[[ + module('pl.template') +]] + +local utils = require 'pl.utils' +local append,format = table.insert,string.format + +local function parseHashLines(chunk,brackets,esc) + local exec_pat = "()$(%b"..brackets..")()" + + local function parseDollarParen(pieces, chunk, s, e) + local s = 1 + for term, executed, e in chunk:gmatch (exec_pat) do + executed = '('..executed:sub(2,-2)..')' + append(pieces, + format("%q..(%s or '')..",chunk:sub(s, term - 1), executed)) + s = e + end + append(pieces, format("%q", chunk:sub(s))) + end + + local esc_pat = esc.."+([^\n]*\n?)" + local esc_pat1, esc_pat2 = "^"..esc_pat, "\n"..esc_pat + local pieces, s = {"return function(_put) ", n = 1}, 1 + while true do + local ss, e, lua = chunk:find (esc_pat1, s) + if not e then + ss, e, lua = chunk:find(esc_pat2, s) + append(pieces, "_put(") + parseDollarParen(pieces, chunk:sub(s, ss)) + append(pieces, ")") + if not e then break end + end + append(pieces, lua) + s = e + 1 + end + append(pieces, " end") + return table.concat(pieces) +end + +local template = {} + +--- expand the template using the specified environment. +-- @param str the template string +-- @param env the environment (by default empty).
    +-- There are three special fields in the environment table
      +--
    • _parent continue looking up in this table
    • +--
    • _brackets; default is '()', can be any suitable bracket pair
    • +--
    • _escape; default is '#'
    • +--
    +function template.substitute(str,env) + env = env or {} + if rawget(env,"_parent") then + setmetatable(env,{__index = env._parent}) + end + local brackets = rawget(env,"_brackets") or '()' + local escape = rawget(env,"_escape") or '#' + local code = parseHashLines(str,brackets,escape) + local fn,err = utils.load(code,'TMP','t',env) + if not fn then return nil,err end + fn = fn() + local out = {} + local res,err = xpcall(function() fn(function(s) + out[#out+1] = s + end) end,debug.traceback) + if not res then + if env._debug then print(code) end + return nil,err + end + return table.concat(out) +end + +return template + + + + diff --git a/Utils/luarocks/share/lua/5.1/pl/test.lua b/Utils/luarocks/share/lua/5.1/pl/test.lua new file mode 100644 index 000000000..162337968 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/test.lua @@ -0,0 +1,116 @@ +--- Useful test utilities. +-- @module pl.test + +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local pretty = require 'pl.pretty' +local path = require 'pl.path' +local print,type = print,type +local clock = os.clock +local debug = require 'debug' +local io,debug = io,debug + +local function dump(x) + if type(x) == 'table' and not (getmetatable(x) and getmetatable(x).__tostring) then + return pretty.write(x,' ',true) + else + return tostring(x) + end +end + +local test = {} + +local function complain (x,y,msg) + local i = debug.getinfo(3) + local err = io.stderr + err:write(path.basename(i.short_src)..':'..i.currentline..': assertion failed\n') + err:write("got:\t",dump(x),'\n') + err:write("needed:\t",dump(y),'\n') + utils.quit(1,msg or "these values were not equal") +end + +--- like assert, except takes two arguments that must be equal and can be tables. +-- If they are plain tables, it will use tablex.deepcompare. +-- @param x any value +-- @param y a value equal to x +-- @param eps an optional tolerance for numerical comparisons +function test.asserteq (x,y,eps) + local res = x == y + if not res then + res = tablex.deepcompare(x,y,true,eps) + end + if not res then + complain(x,y) + end +end + +--- assert that the first string matches the second. +-- @param s1 a string +-- @param s2 a string +function test.assertmatch (s1,s2) + if not s1:match(s2) then + complain (s1,s2,"these strings did not match") + end +end + +function test.assertraise(fn,e) + local ok, err = pcall(unpack(fn)) + if not err or err:match(e)==nil then + complain (err,e,"these errors did not match") + end +end + +--- a version of asserteq that takes two pairs of values. +-- x1==y1 and x2==y2 must be true. Useful for functions that naturally +-- return two values. +-- @param x1 any value +-- @param x2 any value +-- @param y1 any value +-- @param y2 any value +function test.asserteq2 (x1,x2,y1,y2) + if x1 ~= y1 then complain(x1,y1) end + if x2 ~= y2 then complain(x2,y2) end +end + +-- tuple type -- + +local tuple_mt = {} + +function tuple_mt.__tostring(self) + local ts = {} + for i=1, self.n do + local s = self[i] + ts[i] = type(s) == 'string' and string.format('%q', s) or tostring(s) + end + return 'tuple(' .. table.concat(ts, ', ') .. ')' +end + +function tuple_mt.__eq(a, b) + if a.n ~= b.n then return false end + for i=1, a.n do + if a[i] ~= b[i] then return false end + end + return true +end + +--- encode an arbitrary argument list as a tuple. +-- This can be used to compare to other argument lists, which is +-- very useful for testing functions which return a number of values. +-- @usage asserteq(tuple( ('ab'):find 'a'), tuple(1,1)) +function test.tuple(...) + return setmetatable({n=select('#', ...), ...}, tuple_mt) +end + +--- Time a function. Call the function a given number of times, and report the number of seconds taken, +-- together with a message. Any extra arguments will be passed to the function. +-- @param msg a descriptive message +-- @param n number of times to call the function +-- @param fun the function +-- @param ... optional arguments to fun +function test.timer(msg,n,fun,...) + local start = clock() + for i = 1,n do fun(...) end + utils.printf("%s: took %7.2f sec\n",msg,clock()-start) +end + +return test diff --git a/Utils/luarocks/share/lua/5.1/pl/text.lua b/Utils/luarocks/share/lua/5.1/pl/text.lua new file mode 100644 index 000000000..4c09c7003 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/text.lua @@ -0,0 +1,241 @@ +--- Text processing utilities.

    +-- This provides a Template class (modeled after the same from the Python +-- libraries, see string.Template). It also provides similar functions to those +-- found in the textwrap module. +-- See the Guide. +--

    +-- Calling text.format_operator() overloads the % operator for strings to give Python/Ruby style formated output. +-- This is extended to also do template-like substitution for map-like data. +--

    +-- > require 'pl.text'.format_operator()
    +-- > = '%s = %5.3f' % {'PI',math.pi}
    +-- PI = 3.142
    +-- > = '$name = $value' % {name='dog',value='Pluto'}
    +-- dog = Pluto
    +-- 
    +-- @class module +-- @name pl.text + +local gsub = string.gsub +local concat,append = table.concat,table.insert +local utils = require 'pl.utils' +local bind1,usplit,assert_arg,is_callable = utils.bind1,utils.split,utils.assert_arg,utils.is_callable + +local function lstrip(str) return (str:gsub('^%s+','')) end +local function strip(str) return (lstrip(str):gsub('%s+$','')) end +local function make_list(l) return setmetatable(l,utils.stdmt.List) end +local function split(s,delim) return make_list(usplit(s,delim)) end + +local function imap(f,t,...) + local res = {} + for i = 1,#t do res[i] = f(t[i],...) end + return res +end + +--[[ +module ('pl.text',utils._module) +]] + +local text = {} + +local function _indent (s,sp) + local sl = split(s,'\n') + return concat(imap(bind1('..',sp),sl),'\n')..'\n' +end + +--- indent a multiline string. +-- @param s the string +-- @param n the size of the indent +-- @param ch the character to use when indenting (default ' ') +-- @return indented string +function text.indent (s,n,ch) + assert_arg(1,s,'string') + assert_arg(2,s,'number') + return _indent(s,string.rep(ch or ' ',n)) +end + +--- dedent a multiline string by removing any initial indent. +-- useful when working with [[..]] strings. +-- @param s the string +-- @return a string with initial indent zero. +function text.dedent (s) + assert_arg(1,s,'string') + local sl = split(s,'\n') + local i1,i2 = sl[1]:find('^%s*') + sl = imap(string.sub,sl,i2+1) + return concat(sl,'\n')..'\n' +end + +--- format a paragraph into lines so that they fit into a line width. +-- It will not break long words, so lines can be over the length +-- to that extent. +-- @param s the string +-- @param width the margin width, default 70 +-- @return a list of lines +function text.wrap (s,width) + assert_arg(1,s,'string') + width = width or 70 + s = s:gsub('\n',' ') + local i,nxt = 1 + local lines,line = {} + while i < #s do + nxt = i+width + if s:find("[%w']",nxt) then -- inside a word + nxt = s:find('%W',nxt+1) -- so find word boundary + end + line = s:sub(i,nxt) + i = i + #line + append(lines,strip(line)) + end + return make_list(lines) +end + +--- format a paragraph so that it fits into a line width. +-- @param s the string +-- @param width the margin width, default 70 +-- @return a string +-- @see wrap +function text.fill (s,width) + return concat(text.wrap(s,width),'\n') .. '\n' +end + +local Template = {} +text.Template = Template +Template.__index = Template +setmetatable(Template, { + __call = function(obj,tmpl) + return Template.new(tmpl) + end}) + +function Template.new(tmpl) + assert_arg(1,tmpl,'string') + local res = {} + res.tmpl = tmpl + setmetatable(res,Template) + return res +end + +local function _substitute(s,tbl,safe) + local subst + if is_callable(tbl) then + subst = tbl + else + function subst(f) + local s = tbl[f] + if not s then + if safe then + return f + else + error("not present in table "..f) + end + else + return s + end + end + end + local res = gsub(s,'%${([%w_]+)}',subst) + return (gsub(res,'%$([%w_]+)',subst)) +end + +--- substitute values into a template, throwing an error. +-- This will throw an error if no name is found. +-- @param tbl a table of name-value pairs. +function Template:substitute(tbl) + assert_arg(1,tbl,'table') + return _substitute(self.tmpl,tbl,false) +end + +--- substitute values into a template. +-- This version just passes unknown names through. +-- @param tbl a table of name-value pairs. +function Template:safe_substitute(tbl) + assert_arg(1,tbl,'table') + return _substitute(self.tmpl,tbl,true) +end + +--- substitute values into a template, preserving indentation.
    +-- If the value is a multiline string _or_ a template, it will insert +-- the lines at the correct indentation.
    +-- Furthermore, if a template, then that template will be subsituted +-- using the same table. +-- @param tbl a table of name-value pairs. +function Template:indent_substitute(tbl) + assert_arg(1,tbl,'table') + if not self.strings then + self.strings = split(self.tmpl,'\n') + end + -- the idea is to substitute line by line, grabbing any spaces as + -- well as the $var. If the value to be substituted contains newlines, + -- then we split that into lines and adjust the indent before inserting. + local function subst(line) + return line:gsub('(%s*)%$([%w_]+)',function(sp,f) + local subtmpl + local s = tbl[f] + if not s then error("not present in table "..f) end + if getmetatable(s) == Template then + subtmpl = s + s = s.tmpl + else + s = tostring(s) + end + if s:find '\n' then + s = _indent(s,sp) + end + if subtmpl then return _substitute(s,tbl) + else return s + end + end) + end + local lines = imap(subst,self.strings) + return concat(lines,'\n')..'\n' +end + +------- Python-style formatting operator ------ +-- (see the lua-users wiki) -- + +function text.format_operator() + + local format = string.format + + -- a more forgiving version of string.format, which applies + -- tostring() to any value with a %s format. + local function formatx (fmt,...) + local args = {...} + local i = 1 + for p in fmt:gmatch('%%.') do + if p == '%s' and type(args[i]) ~= 'string' then + args[i] = tostring(args[i]) + end + i = i + 1 + end + return format(fmt,unpack(args)) + end + + local function basic_subst(s,t) + return (s:gsub('%$([%w_]+)',t)) + end + + -- Note this goes further than the original, and will allow these cases: + -- 1. a single value + -- 2. a list of values + -- 3. a map of var=value pairs + -- 4. a function, as in gsub + -- For the second two cases, it uses $-variable substituion. + getmetatable("").__mod = function(a, b) + if b == nil then + return a + elseif type(b) == "table" and getmetatable(b) == nil then + if #b == 0 then -- assume a map-like table + return _substitute(a,b,true) + else + return formatx(a,unpack(b)) + end + elseif type(b) == 'function' then + return basic_subst(a,b) + else + return formatx(a,b) + end + end +end + +return text diff --git a/Utils/luarocks/share/lua/5.1/pl/utils.lua b/Utils/luarocks/share/lua/5.1/pl/utils.lua new file mode 100644 index 000000000..af1553b2f --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/utils.lua @@ -0,0 +1,529 @@ +--- Generally useful routines. +-- See the Guide. +-- @class module +-- @name pl.utils +local format,gsub,byte = string.format,string.gsub,string.byte +local clock = os.clock +local stdout = io.stdout +local append = table.insert + +local collisions = {} + +local utils = {} + +utils._VERSION = "0.9.4" + +utils.dir_separator = _G.package.config:sub(1,1) + +--- end this program gracefully. +-- @param code The exit code or a message to be printed +-- @param ... extra arguments for message's format' +-- @see utils.fprintf +function utils.quit(code,...) + if type(code) == 'string' then + utils.fprintf(io.stderr,code,...) + code = -1 + else + utils.fprintf(io.stderr,...) + end + io.stderr:write('\n') + os.exit(code) +end + +--- print an arbitrary number of arguments using a format. +-- @param fmt The format (see string.format) +-- @param ... Extra arguments for format +function utils.printf(fmt,...) + utils.fprintf(stdout,fmt,...) +end + +--- write an arbitrary number of arguments to a file using a format. +-- @param f File handle to write to. +-- @param fmt The format (see string.format). +-- @param ... Extra arguments for format +function utils.fprintf(f,fmt,...) + utils.assert_string(2,fmt) + f:write(format(fmt,...)) +end + +local function import_symbol(T,k,v,libname) + local key = rawget(T,k) + -- warn about collisions! + if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then + utils.printf("warning: '%s.%s' overrides existing symbol\n",libname,k) + end + rawset(T,k,v) +end + +local function lookup_lib(T,t) + for k,v in pairs(T) do + if v == t then return k end + end + return '?' +end + +local already_imported = {} + +--- take a table and 'inject' it into the local namespace. +-- @param t The Table +-- @param T An optional destination table (defaults to callers environment) +function utils.import(t,T) + T = T or _G + t = t or utils + if type(t) == 'string' then + t = require (t) + end + local libname = lookup_lib(T,t) + if already_imported[t] then return end + already_imported[t] = libname + for k,v in pairs(t) do + import_symbol(T,k,v,libname) + end +end + +utils.patterns = { + FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', + INTEGER = '[+%-%d]%d*', + IDEN = '[%a_][%w_]*', + FILE = '[%a%.\\][:%][%w%._%-\\]*' +} + +--- escape any 'magic' characters in a string +-- @param s The input string +function utils.escape(s) + utils.assert_string(1,s) + return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) +end + +--- return either of two values, depending on a condition. +-- @param cond A condition +-- @param value1 Value returned if cond is true +-- @param value2 Value returned if cond is false (can be optional) +function utils.choose(cond,value1,value2) + if cond then return value1 + else return value2 + end +end + +local raise + +--- return the contents of a file as a string +-- @param filename The file path +-- @param is_bin open in binary mode +-- @return file contents +function utils.readfile(filename,is_bin) + local mode = is_bin and 'b' or '' + utils.assert_string(1,filename) + local f,err = io.open(filename,'r'..mode) + if not f then return utils.raise (err) end + local res,err = f:read('*a') + f:close() + if not res then return raise (err) end + return res +end + +--- write a string to a file +-- @param filename The file path +-- @param str The string +-- @return true or nil +-- @return error message +-- @raise error if filename or str aren't strings +function utils.writefile(filename,str) + utils.assert_string(1,filename) + utils.assert_string(2,str) + local f,err = io.open(filename,'w') + if not f then return raise(err) end + f:write(str) + f:close() + return true +end + +--- return the contents of a file as a list of lines +-- @param filename The file path +-- @return file contents as a table +-- @raise errror if filename is not a string +function utils.readlines(filename) + utils.assert_string(1,filename) + local f,err = io.open(filename,'r') + if not f then return raise(err) end + local res = {} + for line in f:lines() do + append(res,line) + end + f:close() + return res +end + +--- split a string into a list of strings separated by a delimiter. +-- @param s The input string +-- @param re A Lua string pattern; defaults to '%s+' +-- @param plain don't use Lua patterns +-- @param n optional maximum number of splits +-- @return a list-like table +-- @raise error if s is not a string +function utils.split(s,re,plain,n) + utils.assert_string(1,s) + local find,sub,append = string.find, string.sub, table.insert + local i1,ls = 1,{} + if not re then re = '%s+' end + if re == '' then return {s} end + while true do + local i2,i3 = find(s,re,i1,plain) + if not i2 then + local last = sub(s,i1) + if last ~= '' then append(ls,last) end + if #ls == 1 and ls[1] == '' then + return {} + else + return ls + end + end + append(ls,sub(s,i1,i2-1)) + if n and #ls == n then + ls[#ls] = sub(s,i1) + return ls + end + i1 = i3+1 + end +end + +--- split a string into a number of values. +-- @param s the string +-- @param re the delimiter, default space +-- @return n values +-- @usage first,next = splitv('jane:doe',':') +-- @see split +function utils.splitv (s,re) + return unpack(utils.split(s,re)) +end + +local lua52 = table.pack ~= nil +local lua51_load = load + +if not lua52 then -- define Lua 5.2 style load() + function utils.load(str,src,mode,env) + local chunk,err + if type(str) == 'string' then + chunk,err = loadstring(str,src) + else + chunk,err = lua51_load(str,src) + end + if chunk and env then setfenv(chunk,env) end + return chunk,err + end +else + utils.load = load + -- setfenv/getfenv replacements for Lua 5.2 + -- by Sergey Rozhenko + -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html + -- Roberto Ierusalimschy notes that it is possible for getfenv to return nil + -- in the case of a function with no globals: + -- http://lua-users.org/lists/lua-l/2010-06/msg00315.html + function setfenv(f, t) + f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) + local name + local up = 0 + repeat + up = up + 1 + name = debug.getupvalue(f, up) + until name == '_ENV' or name == nil + if name then + debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue + debug.setupvalue(f, up, t) + end + end + + function getfenv(f) + f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) + local name, val + local up = 0 + repeat + up = up + 1 + name, val = debug.getupvalue(f, up) + until name == '_ENV' or name == nil + return val + end +end + + +--- execute a shell command. +-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 +-- @param cmd a shell command +-- @return true if successful +-- @return actual return code +function utils.execute (cmd) + local res1,res2,res2 = os.execute(cmd) + if not lua52 then + return res1==0,res1 + else + return res1,res2 + end +end + +if not lua52 then + function table.pack (...) + local n = select('#',...) + return {n=n; ...},n + end + local sep = package.config:sub(1,1) + function package.searchpath (mod,path) + mod = mod:gsub('%.',sep) + for m in path:gmatch('[^;]+') do + local nm = m:gsub('?',mod) + local f = io.open(nm,'r') + if f then f:close(); return nm end + end + end +end + +if not table.pack then table.pack = _G.pack end +if not rawget(_G,"pack") then _G.pack = table.pack end + +--- take an arbitrary set of arguments and make into a table. +-- This returns the table and the size; works fine for nil arguments +-- @param ... arguments +-- @return table +-- @return table size +-- @usage local t,n = utils.args(...) + +--- 'memoize' a function (cache returned value for next call). +-- This is useful if you have a function which is relatively expensive, +-- but you don't know in advance what values will be required, so +-- building a table upfront is wasteful/impossible. +-- @param func a function of at least one argument +-- @return a function with at least one argument, which is used as the key. +function utils.memoize(func) + return setmetatable({}, { + __index = function(self, k, ...) + local v = func(k,...) + self[k] = v + return v + end, + __call = function(self, k) return self[k] end + }) +end + +--- is the object either a function or a callable object?. +-- @param obj Object to check. +function utils.is_callable (obj) + return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call +end + +--- is the object of the specified type?. +-- If the type is a string, then use type, otherwise compare with metatable +-- @param obj An object to check +-- @param tp String of what type it should be +function utils.is_type (obj,tp) + if type(tp) == 'string' then return type(obj) == tp end + local mt = getmetatable(obj) + return tp == mt +end + +local fileMT = getmetatable(io.stdout) + +--- a string representation of a type. +-- For tables with metatables, we assume that the metatable has a `_name` +-- field. Knows about Lua file objects. +-- @param obj an object +-- @return a string like 'number', 'table' or 'List' +function utils.type (obj) + local t = type(obj) + if t == 'table' or t == 'userdata' then + local mt = getmetatable(obj) + if mt == fileMT then + return 'file' + else + return mt._name or "unknown "..t + end + else + return t + end +end + +--- is this number an integer? +-- @param x a number +-- @raise error if x is not a number +function utils.is_integer (x) + return math.ceil(x)==x +end + +utils.stdmt = { + List = {_name='List'}, Map = {_name='Map'}, + Set = {_name='Set'}, MultiMap = {_name='MultiMap'} +} + +local _function_factories = {} + +--- associate a function factory with a type. +-- A function factory takes an object of the given type and +-- returns a function for evaluating it +-- @param mt metatable +-- @param fun a callable that returns a function +function utils.add_function_factory (mt,fun) + _function_factories[mt] = fun +end + +local function _string_lambda(f) + local raise = utils.raise + if f:find '^|' or f:find '_' then + local args,body = f:match '|([^|]*)|(.+)' + if f:find '_' then + args = '_' + body = f + else + if not args then return raise 'bad string lambda' end + end + local fstr = 'return function('..args..') return '..body..' end' + local fn,err = loadstring(fstr) + if not fn then return raise(err) end + fn = fn() + return fn + else return raise 'not a string lambda' + end +end + +--- an anonymous function as a string. This string is either of the form +-- '|args| expression' or is a function of one argument, '_' +-- @param lf function as a string +-- @return a function +-- @usage string_lambda '|x|x+1' (2) == 3 +-- @usage string_lambda '_+1 (2) == 3 +utils.string_lambda = utils.memoize(_string_lambda) + +local ops + +--- process a function argument. +-- This is used throughout Penlight and defines what is meant by a function: +-- Something that is callable, or an operator string as defined by pl.operator, +-- such as '>' or '#'. If a function factory has been registered for the type, it will +-- be called to get the function. +-- @param idx argument index +-- @param f a function, operator string, or callable object +-- @param msg optional error message +-- @return a callable +-- @raise if idx is not a number or if f is not callable +-- @see utils.is_callable +function utils.function_arg (idx,f,msg) + utils.assert_arg(1,idx,'number') + local tp = type(f) + if tp == 'function' then return f end -- no worries! + -- ok, a string can correspond to an operator (like '==') + if tp == 'string' then + if not ops then ops = require 'pl.operator'.optable end + local fn = ops[f] + if fn then return fn end + local fn, err = utils.string_lambda(f) + if not fn then error(err..': '..f) end + return fn + elseif tp == 'table' or tp == 'userdata' then + local mt = getmetatable(f) + if not mt then error('not a callable object',2) end + local ff = _function_factories[mt] + if not ff then + if not mt.__call then error('not a callable object',2) end + return f + else + return ff(f) -- we have a function factory for this type! + end + end + if not msg then msg = " must be callable" end + if idx > 0 then + error("argument "..idx..": "..msg,2) + else + error(msg,2) + end +end + +--- bind the first argument of the function to a value. +-- @param fn a function of at least two values (may be an operator string) +-- @param p a value +-- @return a function such that f(x) is fn(p,x) +-- @raise same as @{function_arg} +-- @see pl.func.curry +function utils.bind1 (fn,p) + fn = utils.function_arg(1,fn) + return function(...) return fn(p,...) end +end + +--- assert that the given argument is in fact of the correct type. +-- @param n argument index +-- @param val the value +-- @param tp the type +-- @param verify an optional verfication function +-- @param msg an optional custom message +-- @param lev optional stack position for trace, default 2 +-- @raise if the argument n is not the correct type +-- @usage assert_arg(1,t,'table') +-- @usage assert_arg(n,val,'string',path.isdir,'not a directory') +function utils.assert_arg (n,val,tp,verify,msg,lev) + if type(val) ~= tp then + error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),2) + end + if verify and not verify(val) then + error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) + end +end + +--- assert the common case that the argument is a string. +-- @param n argument index +-- @param val a value that must be a string +-- @raise val must be a string +function utils.assert_string (n,val) + utils.assert_arg(n,val,'string',nil,nil,nil,3) +end + +local err_mode = 'default' + +--- control the error strategy used by Penlight. +-- Controls how utils.raise works; the default is for it +-- to return nil and the error string, but if the mode is 'error' then +-- it will throw an error. If mode is 'quit' it will immediately terminate +-- the program. +-- @param mode - either 'default', 'quit' or 'error' +-- @see utils.raise +function utils.on_error (mode) + err_mode = mode +end + +--- used by Penlight functions to return errors. Its global behaviour is controlled +-- by utils.on_error +-- @param err the error string. +-- @see utils.on_error +function utils.raise (err) + if err_mode == 'default' then return nil,err + elseif err_mode == 'quit' then utils.quit(err) + else error(err,2) + end +end + +raise = utils.raise + +--- load a code string or bytecode chunk. +-- @param code Lua code as a string or bytecode +-- @param name for source errors +-- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default) +-- @param env the environment for the new chunk (default nil) +-- @return compiled chunk +-- @return error message (chunk is nil) +-- @function utils.load + + +--- Lua 5.2 Compatible Functions +-- @section lua52 + +--- pack an argument list into a table. +-- @param ... any arguments +-- @return a table with field n set to the length +-- @return the length +-- @function table.pack + +------ +-- return the full path where a Lua module name would be matched. +-- @param mod module name, possibly dotted +-- @param path a path in the same form as package.path or package.cpath +-- @see path.package_path +-- @function package.searchpath + +return utils + + diff --git a/Utils/luarocks/share/lua/5.1/pl/xml.lua b/Utils/luarocks/share/lua/5.1/pl/xml.lua new file mode 100644 index 000000000..a452b26a0 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/pl/xml.lua @@ -0,0 +1,676 @@ +--- XML LOM Utilities. +-- This implements some useful things on LOM documents, such as returned by lxp.lom.parse. +-- In particular, it can convert LOM back into XML text, with optional pretty-printing control. +-- It's based on stanza.lua from Prosody http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua) +-- +-- Can be used as a lightweight one-stop-shop for simple XML processing; a simple XML parser is included +-- but the default is to use lxp.lom if it can be found. +--
    +-- Prosody IM
    +-- Copyright (C) 2008-2010 Matthew Wild
    +-- Copyright (C) 2008-2010 Waqas Hussain
    +--
    +-- classic Lua XML parser by Roberto Ierusalimschy.
    +-- modified to output LOM format.
    +-- http://lua-users.org/wiki/LuaXml
    +-- 
    +-- @module pl.xml + +local t_insert = table.insert; +local t_concat = table.concat; +local t_remove = table.remove; +local s_format = string.format; +local s_match = string.match; +local tostring = tostring; +local setmetatable = setmetatable; +local getmetatable = getmetatable; +local pairs = pairs; +local ipairs = ipairs; +local type = type; +local next = next; +local print = print; +local unpack = unpack or table.unpack; +local s_gsub = string.gsub; +local s_char = string.char; +local s_find = string.find; +local os = os; +local pcall,require,io = pcall,require,io +local split = require 'pl.utils'.split + +local _M = {} +local Doc = { __type = "doc" }; +Doc.__index = Doc; + +--- create a new document node. +-- @param tag the tag name +-- @param attr optional attributes (table of name-value pairs) +function _M.new(tag, attr) + local doc = { tag = tag, attr = attr or {}, last_add = {}}; + return setmetatable(doc, Doc); +end + +--- parse an XML document. By default, this uses lxp.lom.parse, but +-- falls back to basic_parse, or if use_basic is true +-- @param text_or_file file or string representation +-- @param is_file whether text_or_file is a file name or not +-- @param use_basic do a basic parse +-- @return a parsed LOM document with the document metatatables set +-- @return nil, error the error can either be a file error or a parse error +function _M.parse(text_or_file, is_file, use_basic) + local parser,status,lom + if use_basic then parser = _M.basic_parse + else + status,lom = pcall(require,'lxp.lom') + if not status then parser = _M.basic_parse else parser = lom.parse end + end + if is_file then + local f,err = io.open(text_or_file) + if not f then return nil,err end + text_or_file = f:read '*a' + f:close() + end + local doc,err = parser(text_or_file) + if not doc then return nil,err end + if lom then + _M.walk(doc,false,function(_,d) + setmetatable(d,Doc) + end) + end + return doc +end + +---- convenient function to add a document node, This updates the last inserted position. +-- @param tag a tag name +-- @param attrs optional set of attributes (name-string pairs) +function Doc:addtag(tag, attrs) + local s = _M.new(tag, attrs); + (self.last_add[#self.last_add] or self):add_direct_child(s); + t_insert(self.last_add, s); + return self; +end + +--- convenient function to add a text node. This updates the last inserted position. +-- @param text a string +function Doc:text(text) + (self.last_add[#self.last_add] or self):add_direct_child(text); + return self; +end + +---- go up one level in a document +function Doc:up() + t_remove(self.last_add); + return self; +end + +function Doc:reset() + local last_add = self.last_add; + for i = 1,#last_add do + last_add[i] = nil; + end + return self; +end + +--- append a child to a document directly. +-- @param child a child node (either text or a document) +function Doc:add_direct_child(child) + t_insert(self, child); +end + +--- append a child to a document at the last element added +-- @param child a child node (either text or a document) +function Doc:add_child(child) + (self.last_add[#self.last_add] or self):add_direct_child(child); + return self; +end + +--accessing attributes: useful not to have to expose implementation (attr) +--but also can allow attr to be nil in any future optimizations + +--- set attributes of a document node. +-- @param t a table containing attribute/value pairs +function Doc:set_attribs (t) + for k,v in pairs(t) do + self.attr[k] = v + end +end + +--- set a single attribute of a document node. +-- @param a attribute +-- @param v its value +function Doc:set_attrib(a,v) + self.attr[a] = v +end + +--- access the attributes of a document node. +function Doc:get_attribs() + return self.attr +end + +--- function to create an element with a given tag name and a set of children. +-- @param tag a tag name +-- @param items either text or a table where the hash part is the attributes and the list part is the children. +function _M.elem(tag,items) + local s = _M.new(tag) + if type(items) == 'string' then items = {items} end + if _M.is_tag(items) then + t_insert(s,items) + elseif type(items) == 'table' then + for k,v in pairs(items) do + if type(k) == 'string' then + s.attr[k] = v + t_insert(s.attr,k) + else + s[k] = v + end + end + end + return s +end + +--- given a list of names, return a number of element constructors. +-- @param list a list of names, or a comma-separated string. +-- @usage local parent,children = doc.tags 'parent,children'
    +-- doc = parent {child 'one', child 'two'} +function _M.tags(list) + local ctors = {} + local elem = _M.elem + if type(list) == 'string' then list = split(list,'%s*,%s*') end + for _,tag in ipairs(list) do + local ctor = function(items) return _M.elem(tag,items) end + t_insert(ctors,ctor) + end + return unpack(ctors) +end + +local templ_cache = {} + +local function is_data(data) + return #data == 0 or type(data[1]) ~= 'table' +end + +local function prepare_data(data) + -- a hack for ensuring that $1 maps to first element of data, etc. + -- Either this or could change the gsub call just below. + for i,v in ipairs(data) do + data[tostring(i)] = v + end +end + +--- create a substituted copy of a document, +-- @param templ may be a document or a string representation which will be parsed and cached +-- @param data a table of name-value pairs or a list of such tables +-- @return an XML document +function Doc.subst(templ, data) + if type(data) ~= 'table' or not next(data) then return nil, "data must be a non-empty table" end + if is_data(data) then + prepare_data(data) + end + if type(templ) == 'string' then + if templ_cache[templ] then + templ = templ_cache[templ] + else + local str,err = templ + templ,err = _M.parse(str) + if not templ then return nil,err end + templ_cache[str] = templ + end + end + local function _subst(item) + return _M.clone(templ,function(s) + return s:gsub('%$(%w+)',item) + end) + end + if is_data(data) then return _subst(data) end + local list = {} + for _,item in ipairs(data) do + prepare_data(item) + t_insert(list,_subst(item)) + end + if data.tag then + list = _M.elem(data.tag,list) + end + return list +end + + +--- get the first child with a given tag name. +-- @param tag the tag name +function Doc:child_with_name(tag) + for _, child in ipairs(self) do + if child.tag == tag then return child; end + end +end + +local _children_with_name +function _children_with_name(self,tag,list,recurse) + for _, child in ipairs(self) do if type(child) == 'table' then + if child.tag == tag then t_insert(list,child) end + if recurse then _children_with_name(child,tag,list,recurse) end + end end +end + +--- get all elements in a document that have a given tag. +-- @param tag a tag name +-- @param dont_recurse optionally only return the immediate children with this tag name +-- @return a list of elements +function Doc:get_elements_with_name(tag,dont_recurse) + local res = {} + _children_with_name(self,tag,res,not dont_recurse) + return res +end + +-- iterate over all children of a document node, including text nodes. +function Doc:children() + local i = 0; + return function (a) + i = i + 1 + return a[i]; + end, self, i; +end + +-- return the first child element of a node, if it exists. +function Doc:first_childtag() + if #self == 0 then return end + for _,t in ipairs(self) do + if type(t) == 'table' then return t end + end +end + +function Doc:matching_tags(tag, xmlns) + xmlns = xmlns or self.attr.xmlns; + local tags = self; + local start_i, max_i = 1, #tags; + return function () + for i=start_i,max_i do + v = tags[i]; + if (not tag or v.tag == tag) + and (not xmlns or xmlns == v.attr.xmlns) then + start_i = i+1; + return v; + end + end + end, tags, i; +end + +--- iterate over all child elements of a document node. +function Doc:childtags() + local i = 0; + return function (a) + local v + repeat + i = i + 1 + v = self[i] + if v and type(v) == 'table' then return v; end + until not v + end, self[1], i; +end + +--- visit child element of a node and call a function, possibility modifying the document. +-- @param callback a function passed the node (text or element). If it returns nil, that node will be removed. +-- If it returns a value, that will replace the current node. +function Doc:maptags(callback) + local is_tag = _M.is_tag + local i = 1; + while i <= #self do + if is_tag(self[i]) then + local ret = callback(self[i]); + if ret == nil then + t_remove(self, i); + else + self[i] = ret; + i = i + 1; + end + end + end + return self; +end + +local xml_escape +do + local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; + function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end + _M.xml_escape = xml_escape; +end + +-- pretty printing +-- if indent, then put each new tag on its own line +-- if attr_indent, put each new attribute on its own line +local function _dostring(t, buf, self, xml_escape, parentns, idn, indent, attr_indent) + local nsid = 0; + local tag = t.tag + local lf,alf = ""," " + if indent then lf = '\n'..idn end + if attr_indent then alf = '\n'..idn..attr_indent end + t_insert(buf, lf.."<"..tag); + for k, v in pairs(t.attr) do + if type(k) ~= 'number' then -- LOM attr table has list-like part + if s_find(k, "\1", 1, true) then + local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); + nsid = nsid + 1; + t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'"); + elseif not(k == "xmlns" and v == parentns) then + t_insert(buf, alf..k.."='"..xml_escape(v).."'"); + end + end + end + local len,has_children = #t; + if len == 0 then + local out = "/>" + if attr_indent then out = '\n'..idn..out end + t_insert(buf, out); + else + t_insert(buf, ">"); + for n=1,len do + local child = t[n]; + if child.tag then + self(child, buf, self, xml_escape, t.attr.xmlns,idn and idn..indent, indent, attr_indent ); + has_children = true + else -- text element + t_insert(buf, xml_escape(child)); + end + end + t_insert(buf, (has_children and lf or '')..""); + end +end + +---- pretty-print an XML document +--- @param t an XML document +--- @param idn an initial indent (indents are all strings) +--- @param indent an indent for each level +--- @param attr_indent if given, indent each attribute pair and put on a separate line +--- @return a string representation +function _M.tostring(t,idn,indent, attr_indent) + local buf = {}; + _dostring(t, buf, _dostring, xml_escape, nil,idn,indent, attr_indent); + return t_concat(buf); +end + +Doc.__tostring = _M.tostring + +--- get the full text value of an element +function Doc:get_text() + local res = {} + for i,el in ipairs(self) do + if type(el) == 'string' then t_insert(res,el) end + end + return t_concat(res); +end + +--- make a copy of a document +-- @param doc the original document +-- @param strsubst an optional function for handling string copying which could do substitution, etc. +function _M.clone(doc, strsubst) + local lookup_table = {}; + local function _copy(object) + if type(object) ~= "table" then + if strsubst and type(object) == 'string' then return strsubst(object) + else return object; + end + elseif lookup_table[object] then + return lookup_table[object]; + end + local new_table = {}; + lookup_table[object] = new_table; + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value); -- is cloning keys much use, hm? + end + return setmetatable(new_table, getmetatable(object)); + end + + return _copy(doc) +end + +--- compare two documents. +-- @param t1 any value +-- @param t2 any value +function _M.compare(t1,t2) + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false, 'type mismatch' end + if ty1 == 'string' then + return t1 == t2 and true or 'text '..t1..' ~= text '..t2 + end + if ty1 ~= 'table' or ty2 ~= 'table' then return false, 'not a document' end + if t1.tag ~= t2.tag then return false, 'tag '..t1.tag..' ~= tag '..t2.tag end + if #t1 ~= #t2 then return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag end + -- compare attributes + for k,v in pairs(t1.attr) do + if t2.attr[k] ~= v then return false, 'mismatch attrib' end + end + for k,v in pairs(t2.attr) do + if t1.attr[k] ~= v then return false, 'mismatch attrib' end + end + -- compare children + for i = 1,#t1 do + local yes,err = _M.compare(t1[i],t2[i]) + if not yes then return err end + end + return true +end + +--- is this value a document element? +-- @param d any value +function _M.is_tag(d) + return type(d) == 'table' and type(d.tag) == 'string' +end + +--- call the desired function recursively over the document. +-- @param doc the document +-- @param depth_first visit child notes first, then the current node +-- @param operation a function which will receive the current tag name and current node. +function _M.walk (doc, depth_first, operation) + if not depth_first then operation(doc.tag,doc) end + for _,d in ipairs(doc) do + if _M.is_tag(d) then + _M.walk(d,depth_first,operation) + end + end + if depth_first then operation(doc.tag,doc) end +end + +local escapes = { quot = "\"", apos = "'", lt = "<", gt = ">", amp = "&" } +local function unescape(str) return (str:gsub( "&(%a+);", escapes)); end + +local function parseargs(s) + local arg = {} + s:gsub("([%w:]+)%s*=%s*([\"'])(.-)%2", function (w, _, a) + arg[w] = unescape(a) + end) + return arg +end + +--- Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version. +-- @param s the XML document to be parsed. +-- @param all_text if true, preserves all whitespace. Otherwise only text containing non-whitespace is included. +function _M.basic_parse(s,all_text) + local t_insert,t_remove = table.insert,table.remove + local s_find,s_sub = string.find,string.sub + local stack = {} + local top = {} + t_insert(stack, top) + local ni,c,label,xarg, empty + local i, j = 1, 1 + -- we're not interested in + local _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*') + if istart then i = istart+1 end + while true do + ni,j,c,label,xarg, empty = s_find(s, "<(%/?)([%w:%-_]+)(.-)(%/?)>", i) + if not ni then break end + local text = s_sub(s, i, ni-1) + if all_text or not s_find(text, "^%s*$") then + t_insert(top, unescape(text)) + end + if empty == "/" then -- empty element tag + t_insert(top, setmetatable({tag=label, attr=parseargs(xarg), empty=1},Doc)) + elseif c == "" then -- start tag + top = setmetatable({tag=label, attr=parseargs(xarg)},Doc) + t_insert(stack, top) -- new level + else -- end tag + local toclose = t_remove(stack) -- remove top + top = stack[#stack] + if #stack < 1 then + error("nothing to close with "..label) + end + if toclose.tag ~= label then + error("trying to close "..toclose.tag.." with "..label) + end + t_insert(top, toclose) + end + i = j+1 + end + local text = s_sub(s, i) + if all_text or not s_find(text, "^%s*$") then + t_insert(stack[#stack], unescape(text)) + end + if #stack > 1 then + error("unclosed "..stack[#stack].tag) + end + local res = stack[1] + return type(res[1])=='string' and res[2] or res[1] +end + +local function empty(attr) return not attr or not next(attr) end +local function is_text(s) return type(s) == 'string' end +local function is_element(d) return type(d) == 'table' and d.tag ~= nil end + +-- returns the key,value pair from a table if it has exactly one entry +local function has_one_element(t) + local key,value = next(t) + if next(t,key) ~= nil then return false end + return key,value +end + +local function append_capture(res,tbl) + if not empty(tbl) then -- no point in capturing empty tables... + local key + if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table + key = tbl._ + tbl._ = nil + if empty(tbl) then return end + end + -- a table with only one pair {[0]=value} shall be reduced to that value + local numkey,val = has_one_element(tbl) + if numkey == 0 then tbl = val end + if key then + res[key] = tbl + else -- otherwise, we append the captured table + t_insert(res,tbl) + end + end +end + +local function make_number(pat) + if pat:find '^%d+$' then -- $1 etc means use this as an array location + pat = tonumber(pat) + end + return pat +end + +local function capture_attrib(res,pat,value) + pat = make_number(pat:sub(2)) + res[pat] = value + return true +end + +local match +function match(d,pat,res,keep_going) + local ret = true + if d == nil then return false end + -- attribute string matching is straight equality, except if the pattern is a $ capture, + -- which always succeeds. + if type(d) == 'string' then + if type(pat) ~= 'string' then return false end + if _M.debug then print(d,pat) end + if pat:find '^%$' then + return capture_attrib(res,pat,d) + else + return d == pat + end + else + if _M.debug then print(d.tag,pat.tag) end + -- this is an element node. For a match to succeed, the attributes must + -- match as well. + -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute + local tagpat = pat.tag:match '^(.-)%-$' + if tagpat then + tagpat = make_number(tagpat) + res[tagpat] = d.tag + end + if d.tag == pat.tag or tagpat then + + if not empty(pat.attr) then + if empty(d.attr) then ret = false + else + for prop,pval in pairs(pat.attr) do + local dval = d.attr[prop] + if not match(dval,pval,res) then ret = false; break end + end + end + end + -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..} + if ret and #pat > 0 then + local i,j = 1,1 + local function next_elem() + j = j + 1 -- next child element of data + if is_text(d[j]) then j = j + 1 end + return j <= #d + end + repeat + local p = pat[i] + -- repeated {{<...>}} patterns shall match one or more elements + -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X} + if is_element(p) and p.repeated then + local found + repeat + local tbl = {} + ret = match(d[j],p,tbl,false) + if ret then + found = false --true + append_capture(res,tbl) + end + until not next_elem() or (found and not ret) + i = i + 1 + else + ret = match(d[j],p,res,false) + if ret then i = i + 1 end + end + until not next_elem() or i > #pat -- run out of elements or patterns to match + -- if every element in our pattern matched ok, then it's been a successful match + if i > #pat then return true end + end + if ret then return true end + else + ret = false + end + -- keep going anyway - look at the children! + if keep_going then + for child in d:childtags() do + ret = match(child,pat,res,keep_going) + if ret then break end + end + end + end + return ret +end + +function Doc:match(pat) + if is_text(pat) then + pat = _M.parse(pat,false,true) + end + _M.walk(pat,false,function(_,d) + if is_text(d[1]) and is_element(d[2]) and is_text(d[3]) and + d[1]:find '%s*{{' and d[3]:find '}}%s*' then + t_remove(d,1) + t_remove(d,2) + d[1].repeated = true + end + end) + + local res = {} + local ret = match(self,pat,res,true) + return res,ret +end + + +return _M + diff --git a/Utils/luarocks/share/lua/5.1/template/file.lua b/Utils/luarocks/share/lua/5.1/template/file.lua new file mode 100644 index 000000000..e562c9d55 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/file.lua @@ -0,0 +1,106 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return[[# +
    +# -- +# -- Module name +# -- +# if _file.name then + Module $(_file.name) +# end +# -- +# -- Descriptions +# -- +# if _file.shortdescription then + $( format(_file.shortdescription) ) +# end +# if _file.description and #_file.description > 0 then + $( format(_file.description) ) +# end +# -- +# -- Handle "@usage" special tag +# -- +#if _file.metadata and _file.metadata.usage then + $( applytemplate(_file.metadata.usage, i+1) ) +#end +# -- +# -- Show quick description of current type +# -- +# +# -- show quick description for globals +# if not isempty(_file.globalvars) then + Global(s) + +# for _, item in sortedpairs(_file.globalvars) do + + + + +# end +
    $( fulllinkto(item) )$( format(item.shortdescription) )
    +# end +# +# -- get type corresponding to this file (module) +# local currenttype +# local typeref = _file:moduletyperef() +# if typeref and typeref.tag == "internaltyperef" then +# local typedef = _file.types[typeref.typename] +# if typedef and typedef.tag == "recordtypedef" then +# currenttype = typedef +# end +# end +# +# -- show quick description type exposed by module +# if currenttype and not isempty(currenttype.fields) then + Type $(currenttype.name) + $( applytemplate(currenttype, i+2, 'index') ) +# end +# -- +# -- Show quick description of other types +# -- +# if _file.types then +# for name, type in sortedpairs( _file.types ) do +# if type ~= currenttype and type.tag == 'recordtypedef' and not isempty(type.fields) then + Type $(name) + $( applytemplate(type, i+2, 'index') ) +# end +# end +# end +# -- +# -- Long description of globals +# -- +# if not isempty(_file.globalvars) then + Global(s) +# for name, item in sortedpairs(_file.globalvars) do + $( applytemplate(item, i+2) ) +# end +# end +# -- +# -- Long description of current type +# -- +# if currenttype then + Type $(currenttype.name) + $( applytemplate(currenttype, i+2) ) +# end +# -- +# -- Long description of other types +# -- +# if not isempty( _file.types ) then +# for name, type in sortedpairs( _file.types ) do +# if type ~= currenttype and type.tag == 'recordtypedef' then + Type $(name) + $( applytemplate(type, i+2) ) +# end +# end +# end +
    +]] diff --git a/Utils/luarocks/share/lua/5.1/template/index.lua b/Utils/luarocks/share/lua/5.1/template/index.lua new file mode 100644 index 000000000..555c2d373 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/index.lua @@ -0,0 +1,28 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return +[[# +#if _index.modules then +
    +

    Module$( #_index.modules > 1 and 's' )

    + +# for _, module in sortedpairs( _index.modules ) do +# if module.tag ~= 'index' then + + + + +# end +# end +
    $( fulllinkto(module) )$( module.description and format(module.shortdescription) )
    +
    +#end ]] diff --git a/Utils/luarocks/share/lua/5.1/template/index/recordtypedef.lua b/Utils/luarocks/share/lua/5.1/template/index/recordtypedef.lua new file mode 100644 index 000000000..8dfdbc4ce --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/index/recordtypedef.lua @@ -0,0 +1,23 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return [[# +# if not isempty(_recordtypedef.fields) then + +# for _, item in sortedpairs( _recordtypedef.fields ) do + + + + +# end +
    $( fulllinkto(item) )$( format(item.shortdescription) )
    +# end +# ]] diff --git a/Utils/luarocks/share/lua/5.1/template/item.lua b/Utils/luarocks/share/lua/5.1/template/item.lua new file mode 100644 index 000000000..122006185 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/item.lua @@ -0,0 +1,167 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return +[[
    +
    +# -- +# -- Resolve item type definition +# -- +# local typedef = _item:resolvetype() + +# -- +# -- Show item type for internal type +# -- +#if _item.type and (not typedef or typedef.tag ~= 'functiontypedef') then +# --Show link only when available +# local link = fulllinkto(_item.type) +# if link then + $( link ) +# else + $(prettyname(_item.type)) +# end +#end + +$( prettyname(_item) ) + +
    +
    +# if _item.shortdescription then + $( format(_item.shortdescription) ) +# end +# if _item.description and #_item.description > 0 then + $( format(_item.description) ) +# end +# +# -- +# -- For function definitions, describe parameters and return values +# -- +#if typedef and typedef.tag == 'functiontypedef' then +# -- +# -- Describe parameters +# -- +# local fdef = typedef +# +# -- Adjust parameter count if first one is 'self' +# local paramcount +# if #fdef.params > 0 and isinvokable(_item) then +# paramcount = #fdef.params - 1 +# else +# paramcount = #fdef.params +# end +# +# -- List parameters +# if paramcount > 0 then + Parameter$( paramcount > 1 and 's' ) +
      +# for position, param in ipairs( fdef.params ) do +# if not (position == 1 and isinvokable(_item)) then +
    • +# local paramline = "" +# if param.type then +# local link = linkto( param.type ) +# local name = prettyname( param.type ) +# if link then +# paramline = paramline .. '' .. name .. "" +# else +# paramline = paramline .. name +# end +# end +# +# paramline = paramline .. " " .. param.name .. " " +# +# if param.optional then +# paramline = paramline .. "optional" .. " " +# end +# if param.hidden then +# paramline = paramline .. "hidden" +# end +# +# paramline = paramline .. ": " +# +# if param.description and #param.description > 0 then +# paramline = paramline .. "\n" .. param.description +# end +# + $( format (paramline)) +
    • +# end +# end +
    +# end +# +# -- +# -- Describe returns types +# -- +# if fdef and #fdef.returns > 0 then + Return value$(#fdef.returns > 1 and 's') +# -- +# -- Format nice type list +# -- +# local function niceparmlist( parlist ) +# local typelist = {} +# for position, type in ipairs(parlist) do +# local link = linkto( type ) +# local name = prettyname( type ) +# if link then +# typelist[#typelist + 1] = ''..name..'' +# else +# typelist[#typelist + 1] = name +# end +# -- Append end separator or separating comma +# typelist[#typelist + 1] = position == #parlist and ':' or ', ' +# end +# return table.concat( typelist ) +# end +# -- +# -- Generate a list if they are several return clauses +# -- +# if #fdef.returns > 1 then +
      +# for position, ret in ipairs(fdef.returns) do +
    1. +# local returnline = ""; +# +# local paramlist = niceparmlist(ret.types) +# if #ret.types > 0 and #paramlist > 0 then +# returnline = "" .. paramlist .. "" +# end +# returnline = returnline .. "\n" .. ret.description + $( format (returnline)) +
    2. +# end +
    +# else +# local paramlist = niceparmlist(fdef.returns[1].types) +# local isreturn = fdef.returns and #fdef.returns > 0 and #paramlist > 0 +# local isdescription = fdef.returns and fdef.returns[1].description and #format(fdef.returns[1].description) > 0 +# +# local returnline = ""; +# -- Show return type if provided +# if isreturn then +# returnline = ""..paramlist.."" +# end +# if isdescription then +# returnline = returnline .. "\n" .. fdef.returns[1].description +# end + $( format(returnline)) +# end +# end +#end +# +#-- +#-- Show usage samples +#-- +#if _item.metadata and _item.metadata.usage then + $( applytemplate(_item.metadata.usage, i) ) +#end +
    +
    ]] diff --git a/Utils/luarocks/share/lua/5.1/template/page.lua b/Utils/luarocks/share/lua/5.1/template/page.lua new file mode 100644 index 000000000..32e1957c4 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/page.lua @@ -0,0 +1,68 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return +[[ + +#if _page.headers and #_page.headers > 0 then + +# for _, header in ipairs(_page.headers) do + $(header) +# end + +#end + +
    +
    + +
    +
    +
    +
    +# -- +# -- Generating lateral menu +# -- + + $( applytemplate(_page.currentmodule) ) +
    + + +]] diff --git a/Utils/luarocks/share/lua/5.1/template/recordtypedef.lua b/Utils/luarocks/share/lua/5.1/template/recordtypedef.lua new file mode 100644 index 000000000..758f1e0b0 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/recordtypedef.lua @@ -0,0 +1,36 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +return [[# +# -- +# -- Descriptions +# -- +#if _recordtypedef.shortdescription and #_recordtypedef.shortdescription > 0 then + $( format( _recordtypedef.shortdescription ) ) +#end +#if _recordtypedef.description and #_recordtypedef.description > 0 then + $( format( _recordtypedef.description ) ) +#end +#-- +#-- Describe usage +#-- +#if _recordtypedef.metadata and _recordtypedef.metadata.usage then + $( applytemplate(_recordtypedef.metadata.usage, i) ) +#end +# -- +# -- Describe type fields +# -- +#if not isempty( _recordtypedef.fields ) then + Field(s) +# for name, item in sortedpairs( _recordtypedef.fields ) do + $( applytemplate(item, i) ) +# end +#end ]] diff --git a/Utils/luarocks/share/lua/5.1/template/usage.lua b/Utils/luarocks/share/lua/5.1/template/usage.lua new file mode 100644 index 000000000..93a8c7148 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/usage.lua @@ -0,0 +1,33 @@ +-------------------------------------------------------------------------------- +-- 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: +-- Marc AUBRY +-- - initial API and implementation +-------------------------------------------------------------------------------- +return[[# +#-- +#-- Show usage samples +#-- +#if _usage then +# if #_usage > 1 then +# -- Show all usages + Usages: +
      +# -- Loop over several usage description +# for _, usage in ipairs(_usage) do +
    • $( securechevrons(usage.description) )
    • +# end +
    +# elseif #_usage == 1 then +# -- Show unique usage sample + Usage: +# local usage = _usage[1] +
    $( securechevrons(usage.description) )
    +# end +#end +#]] diff --git a/Utils/luarocks/share/lua/5.1/template/utils.lua b/Utils/luarocks/share/lua/5.1/template/utils.lua new file mode 100644 index 000000000..f2fd01f40 --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/template/utils.lua @@ -0,0 +1,470 @@ +-------------------------------------------------------------------------------- +-- 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +local apimodel = require 'models.apimodel' + +--- +-- @module docutils +-- Handles link generation, node quick description. +-- +-- Provides: +-- * link generation +-- * anchor generation +-- * node quick description +local M = {} + +function M.isempty(map) + local f = pairs(map) + return f(map) == nil +end + +--- +-- Provide a handling function for all supported anchor types +-- recordtypedef => #(typename) +-- item (field of recordtypedef) => #(typename).itemname +-- item (global) => itemname +M.anchortypes = { + recordtypedef = function (o) return string.format('#(%s)', o.name) end, + item = function(o) + if not o.parent or o.parent.tag == 'file' then + -- Handle items referencing globals + return o.name + elseif o.parent and o.parent.tag == 'recordtypedef' then + -- Handle items included in recordtypedef + local recordtypedef = o.parent + local recordtypedefanchor = M.anchor(recordtypedef) + if not recordtypedefanchor then + return nil, 'Unable to generate anchor for `recordtypedef parent.' + end + return string.format('%s.%s', recordtypedefanchor, o.name) + end + return nil, 'Unable to generate anchor for `item' + end +} + +--- +-- Provides anchor string for an object of API mode +-- +-- @function [parent = #docutils] anchor +-- @param modelobject Object form API model +-- @result #string Anchor for an API model object, this function __may rise an error__ +-- @usage # -- In a template +-- # local anchorname = anchor(someobject) +-- +function M.anchor( modelobject ) + local tag = modelobject.tag + if M.anchortypes[ tag ] then + return M.anchortypes[ tag ](modelobject) + end + return nil, string.format('No anchor available for `%s', tag) +end + +local function getexternalmodule( item ) + -- Get file which contains this item + local file + if item.parent then + if item.parent.tag =='recordtypedef' then + local recordtypedefparent = item.parent.parent + if recordtypedefparent and recordtypedefparent.tag =='file'then + file = recordtypedefparent + end + elseif item.parent.tag =='file' then + file = item.parent + else + return nil, 'Unable to fetch item parent' + end + end + return file +end + +--- +-- Provide a handling function for all supported link types +-- +-- internaltyperef => ##(typename) +-- => #anchor(recordtyperef) +-- externaltyperef => modulename.html##(typename) +-- => linkto(file)#anchor(recordtyperef) +-- file(module) => modulename.html +-- index => index.html +-- recordtypedef => ##(typename) +-- => #anchor(recordtyperef) +-- item (internal field of recordtypedef) => ##(typename).itemname +-- => #anchor(item) +-- item (internal global) => #itemname +-- => #anchor(item) +-- item (external field of recordtypedef) => modulename.html##(typename).itemname +-- => linkto(file)#anchor(item) +-- item (externalglobal) => modulename.html#itemname +-- => linkto(file)#anchor(item) +M.linktypes = { + internaltyperef = function(o) return string.format('##(%s)', o.typename) end, + externaltyperef = function(o) return string.format('%s.html##(%s)', o.modulename, o.typename) end, + file = function(o) return string.format('%s.html', o.name) end, + index = function() return 'index.html' end, + recordtypedef = function(o) + local anchor = M.anchor(o) + if not anchor then + return nil, 'Unable to generate anchor for `recordtypedef.' + end + return string.format('#%s', anchor) + end, + item = function(o) + + -- For every item get anchor + local anchor = M.anchor(o) + if not anchor then + return nil, 'Unable to generate anchor for `item.' + end + + -- Built local link to item + local linktoitem = string.format('#%s', anchor) + + -- + -- For external item, prefix with the link to the module. + -- + -- The "external item" concept is used only here for short/embedded + -- notation purposed. This concept and the `.external` field SHALL NOT + -- be used elsewhere. + -- + if o.external then + + -- Get link to file which contains this item + local file = getexternalmodule( o ) + local linktofile = file and M.linkto( file ) + if not linktofile then + return nil, 'Unable to generate link for external `item.' + end + + -- Built external link to item + linktoitem = string.format("%s%s", linktofile, linktoitem) + end + + return linktoitem + end +} + +--- +-- Generates text for HTML links from API model element +-- +-- @function [parent = #docutils] +-- @param modelobject Object form API model +-- @result #string Links text for an API model element, this function __may rise an error__. +-- @usage # -- In a template +-- Some text +function M.linkto( apiobject ) + local tag = apiobject.tag + if M.linktypes[ tag ] then + return M.linktypes[tag](apiobject) + end + if not tag then + return nil, 'Link generation is impossible as no tag has been provided.' + end + return nil, string.format('No link generation available for `%s.', tag) +end + +--- +-- Provide a handling function for all supported pretty name types +-- primitivetyperef => #typename +-- internaltyperef => #typename +-- externaltyperef => modulename#typename +-- file(module) => modulename +-- index => index +-- recordtypedef => typename +-- item (internal function of recordtypedef) => typename.itemname(param1, param2,...) +-- item (internal func with self of recordtypedef) => typename:itemname(param2) +-- item (internal non func field of recordtypedef) => typename.itemname +-- item (internal func global) => functionname(param1, param2,...) +-- item (internal non func global) => itemname +-- item (external function of recordtypedef) => modulename#typename.itemname(param1, param2,...) +-- item (external func with self of recordtypedef) => modulename#typename:itemname(param2) +-- item (external non func field of recordtypedef) => modulename#typename.itemname +-- item (external func global) => functionname(param1, param2,...) +-- item (external non func global) => itemname +M.prettynametypes = { + primitivetyperef = function(o) return string.format('#%s', o.typename) end, + externaltyperef = function(o) return string.format('%s#%s', o.modulename, o.typename) end, + index = function(o) return "index" end, + file = function(o) return o.name end, + recordtypedef = function(o) return o.name end, + item = function( o ) + + -- Determine item name + -- ---------------------- + local itemname = o.name + + -- Determine scope + -- ---------------------- + local parent = o.parent + local isglobal = parent and parent.tag == 'file' + local isfield = parent and parent.tag == 'recordtypedef' + + -- Determine type name + -- ---------------------- + + local typename = isfield and parent.name + + -- Fetch item definition + -- ---------------------- + -- Get file object + local file + if isglobal then + file = parent + elseif isfield then + file = parent.parent + end + -- Get definition + local definition = o:resolvetype (file) + + + + -- Build prettyname + -- ---------------------- + local prettyname + if not definition or definition.tag ~= 'functiontypedef' then + -- Fields + if isglobal or not typename then + prettyname = itemname + else + prettyname = string.format('%s.%s', typename, itemname) + end + else + -- Functions + -- Build parameter list + local paramlist = {} + local isinvokable = M.isinvokable(o) + for position, param in ipairs(definition.params) do + -- For non global function, when first parameter is 'self', + -- it will not be part of listed parameters + if not (position == 1 and isinvokable and isfield) then + table.insert(paramlist, param.name) + if position ~= #definition.params then + table.insert(paramlist, ', ') + end + end + end + + if isglobal or not typename then + prettyname = string.format('%s(%s)',itemname, table.concat(paramlist)) + else + -- Determine function prefix operator, + -- ':' if 'self' is first parameter, '.' else way + local operator = isinvokable and ':' or '.' + + -- Append function parameters + prettyname = string.format('%s%s%s(%s)',typename, operator, itemname, table.concat(paramlist)) + end + end + + -- Manage external Item prettyname + -- ---------------------- + local externalmodule = o.external and getexternalmodule( o ) + local externalmodulename = externalmodule and externalmodule.name + + if externalmodulename then + return string.format('%s#%s',externalmodulename,prettyname) + else + return prettyname + end + end +} +M.prettynametypes.internaltyperef = M.prettynametypes.primitivetyperef + +--- +-- Check if the given item is a function that can be invoked +function M.isinvokable(item) + --test if the item is global + if item.parent and item.parent.tag == 'file' then + return false + end + -- check first param + local definition = item:resolvetype() + if definition and definition.tag == 'functiontypedef' then + if (#definition.params > 0) then + return definition.params[1].name == 'self' + end + end +end + +--- +-- Provide human readable overview from an API model element +-- +-- Resolve all element needed to summurize nicely an element form API model. +-- @usage $ print( prettyname(item) ) +-- module:somefunction(secondparameter) +-- @function [parent = #docutils] +-- @param apiobject Object form API model +-- @result #string Human readable description of given element. +-- @result #nil, #string In case of error. +function M.prettyname( apiobject ) + local tag = apiobject.tag + if M.prettynametypes[tag] then + return M.prettynametypes[tag](apiobject) + elseif not tag then + return nil, 'No pretty name available as no tag has been provided.' + end + return nil, string.format('No pretty name for `%s.', tag) +end + +--- +-- Just make a string print table in HTML. +-- @function [parent = #docutils] securechevrons +-- @param #string String to convert. +-- @usage securechevrons('') => '<markup>' +-- @return #string Converted string. +function M.securechevrons( str ) + if not str then return nil, 'String expected.' end + return string.gsub(str:gsub('<', '<'), '>', '>') +end + +------------------------------------------------------------------------------- +-- Handling format of @{some#type} tag. +-- Following functions enable to recognize several type of references between +-- "{}". +------------------------------------------------------------------------------- + +--- +-- Provide API Model elements from string describing global elements +-- such as: +-- * `global#foo` +-- * `foo#global.bar` +local globals = function(str) + -- Handling globals from modules + for modulename, fieldname in str:gmatch('([%a%.%d_]+)#global%.([%a%.%d_]+)') do + local item = apimodel._item(fieldname) + local file = apimodel._file() + file.name = modulename + file:addglobalvar( item ) + return item + end + -- Handling other globals + for name in str:gmatch('global#([%a%.%d_]+)') do + -- print("globale", name) + return apimodel._externaltypref('global', name) + end + return nil +end + +--- +-- Transform a string like `module#(type).field` in an API Model item +local field = function( str ) + + -- Match `module#type.field` + local mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#([%a%.%d_]+)%.([%a%.%d_]+)')() + + -- Try matching `module#(type).field` + if not mod then + mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#%(([%a%.%d_]+)%)%.([%a%.%d_]+)')() + if not mod then + -- No match + return nil + end + end + + -- Build according `item + local modulefielditem = apimodel._item( fieldname ) + local moduletype = apimodel._recordtypedef(typename) + moduletype:addfield( modulefielditem ) + local typeref + if #mod > 0 then + local modulefile = apimodel._file() + modulefile.name = mod + modulefile:addtype( moduletype ) + typeref = apimodel._externaltypref(mod, typename) + modulefielditem.external = true + else + typeref = apimodel._internaltyperef(typename) + end + modulefielditem.type = typeref + return modulefielditem +end + +--- +-- Build an API internal reference from a string like: `#typeref` +local internal = function ( typestring ) + for name in typestring:gmatch('#([%a%.%d_]+)') do + -- Do not handle this name is it starts with reserved name "global" + if name:find("global.") == 1 then return nil end + return apimodel._internaltyperef(name) + end + return nil +end + +--- +-- Build an API external reference from a string like: `mod.ule#type` +local extern = function (type) + + -- Match `mod.ule#ty.pe` + local modulename, typename = type:gmatch('([%a%.%d_]+)#([%a%.%d_]+)')() + + -- Trying `mod.ule#(ty.pe)` + if not modulename then + modulename, typename = type:gmatch('([%a%.%d_]+)#%(([%a%.%d_]+)%)')() + + -- No match at all + if not modulename then + return nil + end + end + return apimodel._externaltypref(modulename, typename) +end + +--- +-- Build an API external reference from a string like: `mod.ule` +local file = function (type) + for modulename in type:gmatch('([%a%.%d_]+)') do + local file = apimodel._file() + file.name = modulename + return file + end + return nil +end + + +--- +-- Provide API Model element from a string +-- @usage local externaltyperef = getelement("somemodule#somefield") +function M.getelement( str ) + + -- Order matters, more restrictive are at begin of table + local extractors = { + globals, + field, + extern, + internal, + file + } + -- Loop over extractors. + -- First valid result is used + for _, extractor in ipairs( extractors ) do + local result = extractor( str ) + if result then return result end + end + return nil +end + +-------------------------------------------------------------------------------- +-- Iterator that iterates on the table in key ascending order. +-- +-- @function [parent=#utils.table] sortedPairs +-- @param t table to iterate. +-- @return iterator function. +function M.sortedpairs(t) + local a = {} + local insert = table.insert + for n in pairs(t) do insert(a, n) end + table.sort(a) + local i = 0 + return function() + i = i + 1 + return a[i], t[a[i]] + end +end +return M diff --git a/Utils/luarocks/share/lua/5.1/templateengine.lua b/Utils/luarocks/share/lua/5.1/templateengine.lua new file mode 100644 index 000000000..efa976b8a --- /dev/null +++ b/Utils/luarocks/share/lua/5.1/templateengine.lua @@ -0,0 +1,116 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2011-2012 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 +-- - initial API and implementation and initial documentation +-------------------------------------------------------------------------------- +--- +-- This library provide html description of elements from the externalapi +local M = {} + +-- Load template engine +local pltemplate = require 'pl.template' + +-- Markdown handling +local markdown = require 'markdown' + +-- apply template to the given element +function M.applytemplate(elem, ident, templatetype) + -- define environment + local env = M.getenv(elem, ident) + + -- load template + local template = M.gettemplate(elem,templatetype) + if not template then + templatetype = templatetype and string.format(' "%s"', templatetype) or '' + local elementname = string.format(' for %s', elem.tag or 'untagged element') + error(string.format('Unable to load %s template %s', templatetype, elementname)) + end + + -- apply template + local str, err = pltemplate.substitute(template, env) + + --manage errors + if not str then + local templateerror = templatetype and string.format(' parsing "%s" template ', templatetype) or '' + error(string.format('An error occured%s for "%s"\n%s',templateerror, elem.tag, err)) + end + return str +end + +-- get the a new environment for this element +function M.getenv(elem, ident) + local currentenv ={} + for k,v in pairs(M.env) do currentenv[k] = v end + if elem and elem.tag then + currentenv['_'..elem.tag]= elem + end + currentenv['i']= ident or 1 + return currentenv +end + +-- get the template for this element +function M.gettemplate(elem,templatetype) + local tag = elem and elem.tag + if tag then + if templatetype then + return require ("template." .. templatetype.. "." .. tag) + else + return require ("template." .. tag) + end + end +end + + +--- +-- Allow user to format text in descriptions. +-- Default implementation replaces @{---} tags with links and apply markdown. +-- @return #string +local function format(string) + -- Allow to replace encountered tags with valid links + local replace = function(found) + local apiobj = M.env.getelement(found) + if apiobj then + return M.env.fulllinkto(apiobj) + end + return found + end + string = string:gsub('@{%s*(.-)%s*}', replace) + return M.env.markdown( string ) +end +--- +-- Provide a full link to an element using `prettyname` and `linkto`. +-- Default implementation is for HTML. +local function fulllinkto(o) + local ref = M.env.linkto(o) + local name = M.env.prettyname(o) + if not ref then + return name + end + return string.format('%s', ref, name) +end +-- +-- Define default template environnement +-- +local defaultenv = { + table = table, + ipairs = ipairs, + pairs = pairs, + markdown = markdown, + applytemplate = M.applytemplate, + format = format, + linkto = function(str) return str end, + fulllinkto = fulllinkto, + prettyname = function(s) return s end, + getelement = function(s) return nil end +} + +-- this is the global env accessible in the templates +-- env should be redefine by docgenerator user to add functions or redefine it. +M.env = defaultenv +return M diff --git a/Utils/luasrcdiet.bat b/Utils/luasrcdiet.bat new file mode 100644 index 000000000..9534f97f7 --- /dev/null +++ b/Utils/luasrcdiet.bat @@ -0,0 +1,3 @@ +@echo on +"%~dp0luarocks/lua5.1.exe" -e "package.path=\"%~dp0luarocks/share/lua/5.1/?.lua;%~dp0luarocks/share/lua/5.1/?/init.lua;\"..package.path; package.cpath=\"%~dp0luarocks/lib/lua/5.1/?.dll;%~dp0luarocks/systree/lib/lua/5.1/?.dll;\"..package.cpath" -e "local k,l,_=pcall(require,\"luarocks.loader\") _=k and l.add_context(\"luasrcdiet\",\"0.3.0-2\")" "%~dp0luarocks/luasrcdiet/0.3.0-2/bin/luasrcdiet" %* + diff --git a/docs/Documentation/AI_A2A.html b/docs/Documentation/AI_A2A.html index ee2dc9007..5849fab1f 100644 --- a/docs/Documentation/AI_A2A.html +++ b/docs/Documentation/AI_A2A.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -146,6 +157,12 @@ AI_A2A:ClearTargetDistance() + + + + AI_A2A.DisengageRadius + + @@ -164,18 +181,6 @@ AI_A2A.IdleCount - - - - AI_A2A:ManageDamage(PatrolDamageTreshold) - -

    When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.

    - - - - AI_A2A:ManageFuel(PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime) - -

    When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.

    @@ -188,6 +193,12 @@ AI_A2A:OnAfterRTB(Controllable, From, Event, To)

    OnAfter Transition Handler for Event RTB.

    + + + + AI_A2A:OnAfterRefuel(Controllable, From, Event, To) + +

    Refuel Handler OnAfter for AI_A2A

    @@ -212,6 +223,12 @@ AI_A2A:OnBeforeRTB(Controllable, From, Event, To)

    OnBefore Transition Handler for Event RTB.

    + + + + AI_A2A:OnBeforeRefuel(Controllable, From, Event, To) + +

    Refuel Handler OnBefore for AI_A2A

    @@ -281,7 +298,7 @@ - AI_A2A.PatrolDamageTreshold + AI_A2A.PatrolDamageThreshold @@ -293,7 +310,7 @@ - AI_A2A.PatrolFuelTresholdPercentage + AI_A2A.PatrolFuelThresholdPercentage @@ -335,7 +352,25 @@ - AI_A2A.RTBRoute(AIGroup) + AI_A2A.RTBHold(AIGroup, Fsm) + + + + + + AI_A2A.RTBRoute(AIGroup, Fsm) + + + + + + AI_A2A:Refuel() + +

    Refuel Trigger for AI_A2A

    + + + + AI_A2A.Resume(AIGroup, Fsm) @@ -344,12 +379,30 @@ AI_A2A:SetAltitude(PatrolFloorAltitude, PatrolCeilingAltitude)

    Sets the floor and ceiling altitude of the patrol.

    + + + + AI_A2A:SetDamageThreshold(PatrolDamageThreshold) + +

    When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.

    + + + + AI_A2A:SetDisengageRadius(DisengageRadius) + +

    Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.

    AI_A2A:SetDispatcher(Dispatcher) + + + + AI_A2A:SetFuelThreshold(PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime) + +

    When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.

    @@ -368,6 +421,12 @@ AI_A2A:SetStatusOff()

    Set the status checking off.

    + + + + AI_A2A:SetTanker(TankerName) + +

    Sets to refuel at the given tanker.

    @@ -392,12 +451,24 @@ AI_A2A:Stop()

    Synchronous Event Trigger for Event Stop.

    + + + + AI_A2A.TankerName + + AI_A2A:__RTB(Delay)

    Asynchronous Event Trigger for Event RTB.

    + + + + AI_A2A:__Refuel(Delay) + +

    Refuel Asynchronous Trigger for AI_A2A

    @@ -422,6 +493,12 @@ AI_A2A:onafterDead() + + + + AI_A2A:onafterHold(AIGroup, From, Event, To, HoldTime) + + @@ -434,6 +511,12 @@ AI_A2A:onafterRTB(AIGroup, From, Event, To) + + + + AI_A2A:onafterRefuel(AIGroup, From, Event, To) + + @@ -543,6 +626,20 @@ + + +
    +
    + + + +AI_A2A.DisengageRadius + +
    +
    + + +
    @@ -575,6 +672,7 @@
    + #number AI_A2A.IdleCount @@ -583,78 +681,6 @@ - -
    -
    -
    - - -AI_A2A:ManageDamage(PatrolDamageTreshold) - -
    -
    - -

    When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.

    - - -

    However, damage cannot be foreseen early on. -Therefore, when the damage treshold is reached, -the AI will return immediately to the home base (RTB). -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.

    - -

    Parameter

    -
      -
    • - -

      #number PatrolDamageTreshold : -The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.

      - -
    • -
    -

    Return value

    - -

    #AI_A2A: -self

    - -
    -
    -
    -
    - - -AI_A2A:ManageFuel(PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime) - -
    -
    - -

    When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.

    - - -

    Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. -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.

    - -

    Parameters

    -
      -
    • - -

      #number PatrolFuelTresholdPercentage : -The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.

      - -
    • -
    • - -

      #number PatrolOutOfFuelOrbitTime : -The amount of seconds the out of fuel AIControllable will orbit before returning to the base.

      - -
    • -
    -

    Return value

    - -

    #AI_A2A: -self

    -
    @@ -727,6 +753,43 @@ The To State string.

    + +AI_A2A:OnAfterRefuel(Controllable, From, Event, To) + +
    +
    + +

    Refuel Handler OnAfter for AI_A2A

    + +

    Parameters

    +
      +
    • + +

      Wrapper.Controllable#CONTROLLABLE Controllable : +The Controllable Object managed by the FSM.

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + AI_A2A:OnAfterStart(From, Event, To) @@ -878,6 +941,48 @@ The To State string.

    #boolean: Return false to cancel Transition.

    + +
    +
    +
    + + +AI_A2A:OnBeforeRefuel(Controllable, From, Event, To) + +
    +
    + +

    Refuel Handler OnBefore for AI_A2A

    + +

    Parameters

    +
      +
    • + +

      Wrapper.Controllable#CONTROLLABLE Controllable : +The Controllable Object managed by the FSM.

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + +
    @@ -1257,8 +1362,8 @@ Return false to cancel Transition.

    - -AI_A2A.PatrolDamageTreshold + +AI_A2A.PatrolDamageThreshold
    @@ -1285,8 +1390,8 @@ Return false to cancel Transition.

    - -AI_A2A.PatrolFuelTresholdPercentage + +AI_A2A.PatrolFuelThresholdPercentage
    @@ -1381,20 +1486,90 @@ Return false to cancel Transition.

    - -AI_A2A.RTBRoute(AIGroup) + +AI_A2A.RTBHold(AIGroup, Fsm)
    -

    Parameter

    +

    Parameters

    +
    +
    +
    +
    + + +AI_A2A.RTBRoute(AIGroup, Fsm) + +
    +
    + + + +

    Parameters

    + +
    +
    +
    +
    + + +AI_A2A:Refuel() + +
    +
    + +

    Refuel Trigger for AI_A2A

    + +
    +
    +
    +
    + + +AI_A2A.Resume(AIGroup, Fsm) + +
    +
    + + + +

    Parameters

    +
    @@ -1435,6 +1610,67 @@ self

    + +AI_A2A:SetDamageThreshold(PatrolDamageThreshold) + +
    +
    + +

    When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.

    + + +

    However, damage cannot be foreseen early on. +Therefore, when the damage treshold is reached, +the AI will return immediately to the home base (RTB). +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.

    + +

    Parameter

    +
      +
    • + +

      #number PatrolDamageThreshold : +The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.

      + +
    • +
    +

    Return value

    + +

    #AI_A2A: +self

    + +
    +
    +
    +
    + + +AI_A2A:SetDisengageRadius(DisengageRadius) + +
    +
    + +

    Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.

    + +

    Parameter

    +
      +
    • + +

      #number DisengageRadius : +The disengage range.

      + +
    • +
    +

    Return value

    + +

    #AI_A2A: +self

    + +
    +
    +
    +
    + AI_A2A:SetDispatcher(Dispatcher) @@ -1456,6 +1692,44 @@ self

    + +AI_A2A:SetFuelThreshold(PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime) + +
    +
    + +

    When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.

    + + +

    Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +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.

    + +

    Parameters

    +
      +
    • + +

      #number PatrolFuelThresholdPercentage : +The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.

      + +
    • +
    • + +

      #number PatrolOutOfFuelOrbitTime : +The amount of seconds the out of fuel AIControllable will orbit before returning to the base.

      + +
    • +
    +

    Return value

    + +

    #AI_A2A: +self

    + +
    +
    +
    +
    + AI_A2A:SetHomeAirbase(HomeAirbase) @@ -1533,6 +1807,33 @@ self

    + +AI_A2A:SetTanker(TankerName) + +
    +
    + +

    Sets to refuel at the given tanker.

    + +

    Parameter

    +
      +
    • + +

      Wrapper.Group#GROUP TankerName : +The group name of the tanker as defined within the Mission Editor or spawned.

      + +
    • +
    +

    Return value

    + +

    #AI_A2A: +self

    + +
    +
    +
    +
    + AI_A2A:SetTargetDistance(Coordinate) @@ -1588,6 +1889,20 @@ self

    Synchronous Event Trigger for Event Stop.

    +
    +
    +
    +
    + + + +AI_A2A.TankerName + +
    +
    + + +
    @@ -1615,6 +1930,27 @@ The delay in seconds.

    + +AI_A2A:__Refuel(Delay) + +
    +
    + +

    Refuel Asynchronous Trigger for AI_A2A

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + AI_A2A:__Start(Delay) @@ -1688,6 +2024,47 @@ The delay in seconds.

    + +
    +
    +
    + + +AI_A2A:onafterHold(AIGroup, From, Event, To, HoldTime) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      Wrapper.Group#GROUP AIGroup :

      + +
    • +
    • + +

      From :

      + +
    • +
    • + +

      Event :

      + +
    • +
    • + +

      To :

      + +
    • +
    • + +

      HoldTime :

      + +
    • +
    @@ -1737,6 +2114,42 @@ The delay in seconds.

    +

    Parameters

    + + +
    +
    +
    + + +AI_A2A:onafterRefuel(AIGroup, From, Event, To) + +
    +
    + + +

    Parameters

    diff --git a/docs/Documentation/AI_A2A_Dispatcher.html b/docs/Documentation/AI_A2A_Dispatcher.html index b2cf47842..269af1489 100644 --- a/docs/Documentation/AI_A2A_Dispatcher.html +++ b/docs/Documentation/AI_A2A_Dispatcher.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -113,13 +124,165 @@
    -

    Authors: Sven Van de Velde (FlightControl) rework of GCICAP + introduction of new concepts (squadrons).

    -

    Authors: Stonehouse, SNAFU in terms of the advice, documentation, and the original GCICAP script.

    +

    QUICK START GUIDE

    -

    Contributions:

    +

    There are basically two classes available to model an A2A defense system.

    + +

    AI_A2A_DISPATCHER is the main A2A defense class that models the A2A defense system. +AI_A2A_GCICAP derives or inherits from AI_A2A_DISPATCHER and is a more noob user friendly class, but is less flexible.

    + +

    Before you start using the AI_A2A_DISPATCHER or AI_A2A_GCICAP ask youself the following questions.

    + +

    0. Do I need AI_A2A_DISPATCHER or do I need AI_A2A_GCICAP?

    + +

    AI_A2A_GCICAP, automates a lot of the below questions using the mission editor and requires minimal lua scripting. +But the AI_A2A_GCICAP provides less flexibility and a lot of options are defaulted. +With AI_A2A_DISPATCHER you can setup a much more fine grained A2A defense mechanism, but some more (easy) lua scripting is required.

    + +

    1. Which Coalition am I modeling an A2A defense system for? blue or red?

    + +

    One AI_A2A_DISPATCHER object can create a defense system for one coalition, which is blue or red. +If you want to create a mutual defense system, for both blue and red, then you need to create two AI_A2A_DISPATCHER objects, +each governing their defense system.

    + + +

    2. Which type of EWR will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow).

    + +

    The MOOSE framework leverages the Detection classes to perform the EWR detection. +Several types of Detection classes exist, and the most common characteristics of these classes is that they:

    + +
      +
    • Perform detections from multiple FACs as one co-operating entity.
    • +
    • Communicate with a Head Quarters, which consolidates each detection.
    • +
    • Groups detections based on a method (per area, per type or per unit).
    • +
    • Communicates detections.
    • +
    + +

    3. Which EWR units will be used as part of the detection system? Only Ground or also Airborne?

    + +

    Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. +These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). +Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. +The position of these units is very important as they need to provide enough coverage +to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.

    + +

    4. Is a border required?

    + +

    Is this a cold car or a hot war situation? In case of a cold war situation, a border can be set that will only trigger defenses +if the border is crossed by enemy units.

    + +

    5. What maximum range needs to be checked to allow defenses to engage any attacker?

    + +

    A good functioning defense will have a "maximum range" evaluated to the enemy when CAP will be engaged or GCI will be spawned.

    + +

    6. Which Airbases, Carrier Ships, Farps will take part in the defense system for the Coalition?

    + +

    Carefully plan which airbases will take part in the coalition. Color each airbase in the color of the coalition.

    + +

    7. Which Squadrons will I create and which name will I give each Squadron?

    + +

    The defense system works with Squadrons. Each Squadron must be given a unique name, that forms the key to the defense system. +Several options and activities can be set per Squadron.

    + +

    8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps?

    + +

    Squadrons are placed as the "home base" on an airfield, carrier or farp. +Carefully plan where each Squadron will be located as part of the defense system.

    + +

    9. Which plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron?

    + +

    Per Squadron, one or multiple plane models can be allocated as Templates. +These are late activated groups with one airplane or helicopter that start with a specific name, called the template prefix. +The A2A defense system will select from the given templates a random template to spawn a new plane (group).

    + +

    10. Which payloads, skills and skins will these plane models have?

    + +

    Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, +each having different payloads, skills and skins. +The A2A defense system will select from the given templates a random template to spawn a new plane (group).

    + +

    11. For each Squadron, which will perform CAP?

    + +

    Per Squadron, evaluate which Squadrons will perform CAP. +Not all Squadrons need to perform CAP.

    + +

    12. For each Squadron doing CAP, in which ZONE(s) will the CAP be performed?

    + +

    Per CAP, evaluate where the CAP will be performed, in other words, define the zone. +Near the border or a bit further away?

    + +

    13. For each Squadron doing CAP, which zone types will I create?

    + +

    Per CAP zone, evaluate whether you want:

    + +
      +
    • simple trigger zones
    • +
    • polygon zones
    • +
    • moving zones
    • +
    + +

    Depending on the type of zone selected, a different Zone object needs to be created from a ZONE_ class.

    + +

    14. For each Squadron doing CAP, what are the time intervals and CAP amounts to be performed?

    + +

    For each CAP:

    + +
      +
    • How many CAP you want to have airborne at the same time?
    • +
    • How frequent you want the defense mechanism to check whether to start a new CAP?
    • +
    + +

    15. For each Squadron, which will perform GCI?

    + +

    For each Squadron, evaluate which Squadrons will perform GCI? +Not all Squadrons need to perform GCI.

    + +

    16. For each Squadron, which takeoff method will I use?

    + +

    For each Squadron, evaluate which takeoff method will be used:

    + +
      +
    • Straight from the air
    • +
    • From the runway
    • +
    • From a parking spot with running engines
    • +
    • From a parking spot with cold engines
    • +
    + +

    The default takeoff method is staight in the air.

    + +

    17. For each Squadron, which landing method will I use?

    + +

    For each Squadron, evaluate which landing method will be used:

    + +
      +
    • Despawn near the airbase when returning
    • +
    • Despawn after landing on the runway
    • +
    • Despawn after engine shutdown after landing
    • +
    + +

    The default landing method is despawn when near the airbase when returning.

    + +

    18. For each Squadron, which overhead will I use?

    + +

    For each Squadron, depending on the airplane type (modern, old) and payload, which overhead is required to provide any defense? +In other words, if X attacker airplanes are detected, how many Y defense airplanes need to be spawned per squadron? +The Y is dependent on the type of airplane (era), payload, fuel levels, skills etc. +The overhead is a factor that will calculate dynamically how many Y defenses will be required based on X attackers detected.

    + +

    The default overhead is 1. A value greater than 1, like 1.5 will increase the overhead with 50%, a value smaller than 1, like 0.5 will decrease the overhead with 50%.

    + +

    19. For each Squadron, which grouping will I use?

    + +

    When multiple targets are detected, how will defense airplanes be grouped when multiple defense airplanes are spawned for multiple attackers? +Per one, two, three, four?

    + +

    The default grouping is 1. That means, that each spawned defender will act individually.


    +

    Authors: Sven Van de Velde (FlightControl) rework of GCICAP + introduction of new concepts (squadrons).

    +

    Authors: Stonehouse, SNAFU in terms of the advice, documentation, and the original GCICAP script.

    +

    Global(s)

    @@ -134,20 +297,20 @@ - +
    AI_A2A_DISPATCHER_GCICAPAI_A2A_GCICAP -

    AI_A2A_DISPATCHER_GCICAP class, extends AI#AIA2ADISPATCHER

    +

    AI_A2A_GCICAP class, extends AIA2ADispatcher#AIA2ADISPATCHER

    Banner Image

    -

    The #AIA2ADISPATCHER class is designed to create an automatic air defence system for a coalition.

    +

    The AIA2AGCICAP class is designed to create an automatic air defence system for a coalition setting up GCI and CAP air defenses.

    Type AI_A2A_DISPATCHER

    - + @@ -189,13 +352,19 @@ - + - + + + + + @@ -228,6 +397,12 @@ + + + + @@ -243,7 +418,7 @@ - + @@ -276,6 +451,18 @@ + + + + + + + + @@ -288,6 +475,12 @@ + + + + @@ -345,7 +538,7 @@ - + @@ -435,15 +628,123 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + @@ -459,21 +760,33 @@ - + + + + + + + + + @@ -485,7 +798,7 @@ @@ -543,9 +856,21 @@ - + + + + + + + + + @@ -597,25 +922,43 @@ - + - +
    AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron, Defender)AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron, Defender, Size)
    AI_A2A_DISPATCHER:CountDefendersEngaged(Target)AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection)
    AI_A2A_DISPATCHER:CountDefendersToBeEngaged(DetectedItem, DefenderCount)AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection, DefenderCount) + +
    AI_A2A_DISPATCHER.DefenderDefault AI_A2A_DISPATCHER.Detection +
    AI_A2A_DISPATCHER.DisengageRadius +
    AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem, Target)AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem)

    Creates an GCI task when there are targets for it.

    AI_A2A_DISPATCHER:GetCAPDelay(SquadronName) +
    AI_A2A_DISPATCHER:GetDefaultLanding() +

    Gets the default method at which flights will land and despawn as part of the defense system.

    +
    AI_A2A_DISPATCHER:GetDefaultTakeoff() +

    Gets the default method at which new flights will spawn and take-off as part of the defense system.

    AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender) +
    AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender) +
    AI_A2A_DISPATCHER:New(Detection, GroupingRadius)AI_A2A_DISPATCHER:New(Detection)

    AIA2ADISPATCHER constructor.

    AI_A2A_DISPATCHER:SetDefenderTask(Defender, Type, Fsm, Target)AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit) +

    Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron.

    +
    AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds, CapMaxSeconds) +

    Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing.

    +
    AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold) +

    Set the default damage treshold when defenders will RTB.

    +
    AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) +

    Set the default fuel treshold when defenders will RTB or Refuel in the air.

    +
    AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping) +

    Sets the default grouping of new airplanes spawned.

    +
    AI_A2A_DISPATCHER:SetDefaultLanding(Landing) +

    Defines the default method at which flights will land and despawn as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() +

    Sets flights by default to land and despawn at engine shutdown, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() +

    Sets flights by default to land and despawn at the runway, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() +

    Sets flights by default to land and despawn near the airbase in the air, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead) +

    Defines the default amount of extra planes that will take-off as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff) +

    Defines the default method at which new flights will spawn and take-off as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() +

    Sets flights to by default take-off from the airbase at a cold location, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() +

    Sets flights by default to take-off from the airbase at a hot location, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() +

    Sets flights by default to take-off from the runway, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() +

    Sets flights to default take-off in the air, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) +

    Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.

    +
    AI_A2A_DISPATCHER:SetDefaultTanker(TankerName) +

    Set the default tanker where defenders will Refuel in the air.

    +
    AI_A2A_DISPATCHER:SetDefenderTask(SquadronName, Defender, Type, Fsm, Target)
    AI_A2A_DISPATCHER:SetDefenderTaskTarget(AIGroup, Defender, Target)AI_A2A_DISPATCHER:SetDefenderTaskTarget(AIGroup, Defender, AttackerDetection) +
    AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius) +

    Define the radius to disengage any target when the distance to the home base is larger than the specified meters.

    AI_A2A_DISPATCHER:SetSquadron(SquadronName, AirbaseName, SpawnTemplates, Resources)AI_A2A_DISPATCHER:SetIntercept(InterceptDelay) +
    AI_A2A_DISPATCHER:SetSquadron(SquadronName, AirbaseName, TemplatePrefixes, Resources) +

    This is the main method to define Squadrons programmatically.

    AI_A2A_DISPATCHER:SetSquadronCap(SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType) - +

    Set a CAP for a Squadron.

    AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName, CapLimit, LowInterval, HighInterval, Probability) - +

    Set the squadron CAP parameters.

    +
    AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName, FuelThreshold) +

    Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.

    AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName, Grouping) - +

    Sets the grouping of new airplanes spawned.

    AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName)AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName, TakeoffAltitude)

    Sets flights to take-off in the air, as part of the defense system.

    +
    AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName, TakeoffAltitude) +

    Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.

    +
    AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName, TankerName) +

    Set the squadron tanker where defenders will Refuel in the air.

    AI_A2A_DISPATCHER:onafterENGAGE(From, Event, To, Target, AIGroups)AI_A2A_DISPATCHER:onafterENGAGE(From, Event, To, AttackerDetection, Defenders)
    AI_A2A_DISPATCHER:onafterGCI(From, Event, To, Target, DefendersMissing, AIGroups)AI_A2A_DISPATCHER:onafterGCI(From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies)
    -

    Type AI_A2A_DISPATCHER_GCICAP

    +

    Type AI_A2A_GCICAP

    - + + + + + + + + + + + + +
    AI_A2A_DISPATCHER_GCICAP:New(<, GroupingRadius, EWRPrefixes)AI_A2A_GCICAP.CAPTemplates -

    AIA2ADISPATCHER_GCICAP constructor.

    + +
    AI_A2A_GCICAP:New(EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources) +

    AIA2AGCICAP constructor.

    +
    AI_A2A_GCICAP:NewWithBorder(EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources) +

    AIA2AGCICAP constructor with border.

    +
    AI_A2A_GCICAP.Templates +
    @@ -639,6 +982,20 @@ +
    + +

    Demo Missions

    + +

    AI_A2A_DISPATCHER Demo Missions

    + +
    + +

    YouTube Channel

    + +

    DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System

    + +
    +

    Banner Image

    It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. @@ -650,13 +1007,20 @@ In short it is a plug in very flexible and configurable air defence module for D

    Note that in order to create a two way A2A defense system, two AI_A2A_DISPATCHER defense system may need to be created, for each coalition one. This is a good implementation, because maybe in the future, more coalitions may become available in DCS world.

    +
    + +

    USAGE GUIDE

    +

    1. AI_A2A_DISPATCHER constructor:

    +

    Banner Image

    + +

    The AIA2ADISPATCHER.New() method creates a new AI_A2A_DISPATCHER instance.

    1.1. Define the EWR network:

    -

    As part of the AI_A2A_DISPATCHER constructor, an EWR network must be given as the first parameter. +

    As part of the AI_A2A_DISPATCHER :New() constructor, an EWR network must be given as the first parameter. An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.

    Banner Image

    @@ -682,13 +1046,16 @@ increasing or decreasing the radar coverage of the Early Warning System.

    See the following example to setup an EWR network containing EWR stations and AWACS.

    +

    Banner Image +Banner Image

    +
    -- Define a SET_GROUP object that builds a collection of groups that define the EWR network.
     -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR.
     DetectionSetGroup = SET_GROUP:New()
     DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
     DetectionSetGroup:FilterStart()
     
    --- Setup the detection.
    +-- Setup the detection and group targets to a 30km range!
     Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
     
     -- Setup the A2A dispatcher, and initialize it.
    @@ -697,10 +1064,17 @@ A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )
     
     

    The above example creates a SET_GROUP instance, and stores this in the variable (object) DetectionSetGroup. DetectionSetGroup is then being configured to filter all active groups with a group name starting with DF CCCP AWACS or DF CCCP EWR to be included in the Set. -DetectionSetGroup is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. -Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is choosen, which is 30km. +DetectionSetGroup is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.

    + +

    Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is choosen, which is 30km. The Detection object is then passed to the AIA2ADISPATCHER.New() method to indicate the EWR network configuration and setup the A2A defense detection mechanism.

    +

    You could build a mutual defense system like this:

    + +
    A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red )
    +A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue )
    +
    +

    2. Define the detected target grouping radius:

    The target grouping radius is a property of the Detection object, that was passed to the AI_A2A_DISPATCHER object, but can be changed. @@ -711,28 +1085,68 @@ Typically I suggest to use 30000 for new generation planes and 10000 for older e

    Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!

    -

    3. Set the Engage radius:

    +

    3. Set the Engage Radius:

    -

    Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.

    +

    Define the Engage Radius to engage any target by airborne friendlies, +which are executing cap or returning from an intercept mission.

    Banner Image

    -

    So, if there is a target area detected and reported, +

    If there is a target area detected and reported, then any friendlies that are airborne near this target area, -will be commanded to (re-)engage that target when available (if no other tasks were commanded). -For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -will be considered to receive the command to engage that target area. -You need to evaluate the value of this parameter carefully. -If too small, more intercept missions may be triggered upon detected target areas. -If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.

    +will be commanded to (re-)engage that target when available (if no other tasks were commanded).

    -

    4. Set the Intercept radius or Gci radius:

    +

    For example, if 50000 or 50km is given as a value, then any friendly that is airborne within 50km from the detected target, +will be considered to receive the command to engage that target area.

    + +

    You need to evaluate the value of this parameter carefully:

    + +
      +
    • If too small, more intercept missions may be triggered upon detected target areas.
    • +
    • If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
    • +
    + +

    The default Engage Radius is defined as 100000 or 100km. +Use the method AIA2ADISPATCHER.SetEngageRadius() to set a specific Engage Radius. +The Engage Radius is defined for ALL squadrons which are operational.

    + +

    Demonstration Mission: AID-019 - AI_A2A - Engage Range Test

    + +

    In this example an Engage Radius is set to various values.

    + +
    -- Set 50km as the radius to engage any target by airborne friendlies.
    +A2ADispatcher:SetEngageRadius( 50000 )
    +
    +-- Set 100km as the radius to engage any target by airborne friendlies.
    +A2ADispatcher:SetEngageRadius() -- 100000 is the default value.
    +
    + + +

    4. Set the Ground Controlled Intercept Radius or Gci radius:

    When targets are detected that are still really far off, you don't want the AIA2ADISPATCHER to launch intercepts just yet. -You want it to wait until a certain intercept range is reached, which is the distance of the closest airbase to targer being smaller than the Gci radius. -The Gci radius is by default defined as 200km. This value can be overridden using the method AIA2ADISPATCHER.SetGciRadius(). -Override the default Gci radius when the era of the warfare is early, or, when you don't want to let the AIA2ADISPATCHER react immediately when -a certain border or area is not being crossed.

    +You want it to wait until a certain Gci range is reached, which is the distance of the closest airbase to target +being smaller than the Ground Controlled Intercept radius or Gci radius.

    + +

    The default Gci radius is defined as 200000 or 200km. Override the default Gci radius when the era of the warfare is early, or, +when you don't want to let the AIA2ADISPATCHER react immediately when a certain border or area is not being crossed.

    + +

    Use the method AIA2ADISPATCHER.SetGciRadius() to set a specific controlled ground intercept radius. +The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.

    + +

    Demonstration Mission: AID-013 - AI_A2A - Intercept Test

    + +

    In these examples, the Gci Radius is set to various values:

    + +
    -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) 
    +
    +-- Set 100km as the radius to ground control intercept detected targets from the nearest airbase.
    +A2ADispatcher:SetGciRadius( 100000 )
    +
    +-- Set 200km as the radius to ground control intercept.
    +A2ADispatcher:SetGciRadius() -- 200000 is the default value.
    +

    5. Set the borders:

    @@ -741,7 +1155,8 @@ They should be laid out such that a border area is created between the two coali

    Banner Image

    -

    Define a border area to simulate a cold war scenario and use the method AIA2ADISPATCHER.SetBorderZone() to create a border zone for the dispatcher.

    +

    Define a border area to simulate a cold war scenario. +Use the method AIA2ADISPATCHER.SetBorderZone() to create a border zone for the dispatcher.

    A cold war is one where CAP aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. A hot war is one where CAP aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send CAP and GCI aircraft to attack it.

    @@ -753,6 +1168,24 @@ If a hot war is chosen then no borders actually need to be defi it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition.

    +

    Demonstration Mission: AID-009 - AI_A2A - Border Test

    + +

    In this example a border is set for the CCCP A2A dispatcher:

    + +

    Banner Image

    + +
    -- Setup the A2A dispatcher, and initialize it.
    +A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )
    +
    +-- Setup the border.
    +-- Initialize the dispatcher, setting up a border zone. This is a polygon, 
    +-- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area.
    +-- Any enemy crossing this border will be engaged.
    +
    +CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) )
    +A2ADispatcher:SetBorderZone( CCCPBorderZone )
    +
    +

    6. Squadrons:

    The AI_A2A_DISPATCHER works with Squadrons, that need to be defined using the different methods available.

    @@ -766,7 +1199,7 @@ while defining which plane types are being used by the squadron and how many res
  • Have name (string) that is the identifier or key of the squadron.
  • Have specific plane types.
  • Are located at one airbase.
  • -
  • Have a limited set of resources.
  • +
  • Optionally have a limited set of resources. The default is that squadrons have unlimited resources.
  • The name of the squadron given acts as the squadron key in the AI_A2A_DISPATCHER:Squadron...() methods.

    @@ -782,6 +1215,18 @@ while defining which plane types are being used by the squadron and how many res

    For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft.

    +

    This example defines a couple of squadrons. Note the templates defined within the Mission Editor.

    + +

    Banner Image +Banner Image

    + +
     -- Setup the squadrons.
    + A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 )
    + A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 )
    + A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 )
    + A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 )
    + A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 )
    +

    6.1. Set squadron take-off methods

    @@ -795,6 +1240,8 @@ while defining which plane types are being used by the squadron and how many res
  • AIA2ADISPATCHER.SetSquadronTakeoffFromRunway() will spawn new aircraft at the runway at the airfield.
  • +

    The default landing method is to spawn new aircraft directly in the air.

    +

    Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. The more and the longer aircraft need to taxi at an airfield, the more risk there is that:

    @@ -808,6 +1255,32 @@ The more and the longer aircraft need to taxi at an airfield, the more risk ther

    Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues!

    +

    This example sets the default takeoff method to be from the runway. +And for a couple of squadrons overrides this default method.

    + +
     -- Setup the Takeoff methods
    +
    + -- The default takeoff
    + A2ADispatcher:SetDefaultTakeOffFromRunway()
    +
    + -- The individual takeoff per squadron
    + A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air )
    + A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" )
    + A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" )
    + A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" )
    + A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" )  
    +
    + + +

    6.1. Set Squadron takeoff altitude when spawning new aircraft in the air.

    + +

    In the case of the AIA2ADISPATCHER.SetSquadronTakeoffInAir() there is also an other parameter that can be applied. +That is modifying or setting the altitude from where planes spawn in the air. +Use the method AIA2ADISPATCHER.SetSquadronTakeoffInAirAltitude() to set the altitude for a specific squadron. +The default takeoff altitude can be modified or set using the method AIA2ADISPATCHER.SetSquadronTakeoffInAirAltitude(). +As part of the method AIA2ADISPATCHER.SetSquadronTakeoffInAir() a parameter can be specified to set the takeoff altitude. +If this parameter is not specified, then the default altitude will be used for the squadron.

    +

    6.2. Set squadron landing methods

    In analogy with takeoff, the landing methods are to control how squadrons land at the airfield:

    @@ -825,9 +1298,26 @@ A2A defense system, as no new CAP or GCI planes can takeoff. Note that the method AIA2ADISPATCHER.SetSquadronLandingNearAirbase() will only work for returning aircraft, not for damaged or out of fuel aircraft. Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control.

    +

    This example defines the default landing method to be at the runway. +And for a couple of squadrons overrides this default method.

    + +
     -- Setup the Landing methods
    +
    + -- The default landing method
    + A2ADispatcher:SetDefaultLandingAtRunway()
    +
    + -- The individual landing per squadron
    + A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" )
    + A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" )
    + A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" )
    + A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" )
    + A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway )
    +
    + +

    6.3. Set squadron grouping

    -

    Use the method AIA2ADISPATCHER.SetSquadronGrouping() to set the amount of CAP or GCI flights that will take-off when spawned.

    +

    Use the method AIA2ADISPATCHER.SetSquadronGrouping() to set the grouping of CAP or GCI flights that will take-off when spawned.

    Banner Image

    @@ -836,9 +1326,11 @@ targets to be engaged. Depending on the grouping parameter, the spawned flights For example with a group setting of 2, if 3 targets are detected and cannot be engaged by CAP or any airborne flight, a GCI needs to be started, the GCI flights will be grouped as follows: Group 1 of 2 flights and Group 2 of one flight!

    +

    Even more ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each!

    +

    The grouping value is set for a Squadron, and can be dynamically adjusted during mission execution, so to adjust the defense flights grouping when the tactical situation changes.

    -

    6.4. Balance or setup effectiveness of the air defenses in case of GCI

    +

    6.4. Overhead and Balance the effectiveness of the air defenses in case of GCI.

    The effectiveness can be set with the overhead parameter. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. The default value of the overhead parameter is 1.0, which means equal balance.

    @@ -847,7 +1339,7 @@ The default value of the overhead parameter is 1.0, which means

    However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed.

    -

    The AIA2ADISPATCHER.SetOverhead() method can be used to tweak the defense strength, +

    The AIA2ADISPATCHER.SetSquadronOverhead() method can be used to tweak the defense strength, taking into account the plane types of the squadron.

    For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... @@ -862,8 +1354,18 @@ The overhead must be given as a decimal value with 1 as the neutral value, which

    The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group multiplied by the Overhead and rounded up to the smallest integer.

    +

    For example ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each!

    +

    The overhead value is set for a Squadron, and can be dynamically adjusted during mission execution, so to adjust the defense overhead when the tactical situation changes.

    +

    6.5. Squadron fuel treshold.

    + +

    When an airplane gets out of fuel to a certain %-tage, which is by default 15% (0.15), there are two possible actions that can be taken: + - The defender will go RTB, and will be replaced with a new defender if possible. + - The defender will refuel at a tanker, if a tanker has been specified for the squadron.

    + +

    Use the method AIA2ADISPATCHER.SetSquadronFuelThreshold() to set the squadron fuel treshold of spawned airplanes for all squadrons.

    +

    7. Setup a squadron for CAP

    7.1. Set the CAP zones

    @@ -891,16 +1393,24 @@ multiplied by the Overhead and rounded up to the smallest integer.

    The following example illustrates how CAP zones are coded:

    +

    Banner Image

    +
     -- CAP Squadron execution.
      CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) )
      A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 )
      A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 )
    +
    - CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) +

    Banner Image

    + +
     CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) )
      A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" )
      A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 )
    +
    - CAPZoneMiddle = ZONE:New( "CAP Zone Middle") +

    Banner Image

    + +
     CAPZoneMiddle = ZONE:New( "CAP Zone Middle")
      A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" )
      A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 )
     
    @@ -929,8 +1439,35 @@ Zones can be circles, can be setup in the mission editor using trigger zones, bu

    For example, the following setup will create a CAP for squadron "Sochi":

    -

    A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 )

    +
     A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" )
    + A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 )
    +
    + +

    7.3. Squadron tanker to refuel when executing CAP and defender is out of fuel.

    + +

    Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. +This greatly increases the efficiency of your CAP operations.

    + +

    In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. +Then, use the method AIA2ADISPATCHER.SetDefaultTanker() to set the default tanker for the refuelling. +You can also specify a specific tanker for refuelling for a squadron by using the method AIA2ADISPATCHER.SetSquadronTanker().

    + +

    When the tanker specified is alive and in the air, the tanker will be used for refuelling.

    + +

    For example, the following setup will create a CAP for squadron "Gelend" with a refuel task for the squadron:

    + +

    Banner Image

    + +
     -- Define the CAP
    + A2ADispatcher:SetSquadron( "Gelend", AIRBASE.Caucasus.Gelendzhik, { "SQ CCCP SU-30" }, 20 )
    + A2ADispatcher:SetSquadronCap( "Gelend", ZONE:New( "PatrolZoneGelend" ), 4000, 8000, 600, 800, 1000, 1300 )
    + A2ADispatcher:SetSquadronCapInterval( "Gelend", 2, 30, 600, 1 ) 
    + A2ADispatcher:SetSquadronGci( "Gelend", 900, 1200 )
    +
    + -- Setup the Refuelling for squadron "Gelend", at tanker (group) "TankerGelend" when the fuel in the tank of the CAP defenders is less than 80%.
    + A2ADispatcher:SetSquadronFuelThreshold( "Gelend", 0.8 )
    + A2ADispatcher:SetSquadronTanker( "Gelend", "TankerGelend" )
    +

    8. Setup a squadron for GCI:

    @@ -949,7 +1486,8 @@ too short will mean that the intruders may have alraedy passed the ideal interce

    For example, the following setup will create a GCI for squadron "Sochi":

    -

    A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 )

    +
     A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 )
    +

    9. Other configuration options

    @@ -959,171 +1497,144 @@ too short will mean that the intruders may have alraedy passed the ideal interce Use the method AIA2ADISPATCHER.SetTacticalDisplay() to switch on the tactical display panel. The default will not show this panel. Note that there may be some performance impact if this panel is shown.

    -

    10. Mission Editor Guide:

    +

    10. Defaults settings.

    -

    The following steps need to be followed, in order to setup the different borders, templates and groups within the mission editor:

    +

    This provides a good overview of the different parameters that are setup or hardcoded by default. +For some default settings, a method is available that allows you to tweak the defaults.

    -

    10.1. Define your EWR network:

    +

    10.1. Default takeoff method.

    -

    Banner Image

    +

    The default takeoff method is set to in the air, which means that new spawned airplanes will be spawned directly in the air above the airbase by default.

    -

    At strategic positions within the battlefield, position the correct groups of units that have radar detection capability in the battlefield. -Create the naming of these groups as such, that these can be easily recognized and included as a prefix within your lua MOOSE mission script. -These prefixes should be unique, so that accidentally no other groups would be incorporated within the EWR network.

    - -

    10.2. Define the border zone:

    - -

    Banner Image

    - -

    For a cold war situation, define your border zone. -You can do this in many ways, as the Zone capability within MOOSE outlines. However, the best practice is to create a ZONE_POLYGON class. -To do this, you need to create a zone using a helicopter group, that is late activated, and has a unique group name. -Place the helicopter where the border zone should start, and draw using the waypoints the polygon zone around the area that is considered the border. -The helicopter group name is included as the reference within your lua MOOSE mission script, so ensure that the name is unique and is easily recognizable.

    - -

    10.3. Define the plane templates:

    - -

    Banner Image

    - -

    Define the templates of the planes that define the format of planes that will take part in the A2A defenses of your coalition. -These plane templates will never be activated, but are used to create a diverse airplane portfolio allocated to your squadrons.

    - -

    IMPORTANT! Plane templates MUST be of ONE unit, and must have the Late Activated flag switched on!

    - -

    Plane templates are used to diversify the defending squadrons with:

    +

    The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.

    -

    Place these airplane templates are good visible locations within your mission, so you can easily retrieve them back.

    +

    10.2. Default landing method.

    -

    10.4. Define the CAP zones:

    +

    The default landing method is set to near the airbase, which means that returning airplanes will be despawned directly in the air by default.

    -

    Banner Image

    +

    The default landing method can be set for ALL squadrons that don't have an individual landing method configured.

    -

    Similar as with the border zone, define the CAP zones using helicopter group templates. Its waypoints define the polygon zones. -But you can also define other zone types instead, like moving zones.

    + -

    Banner Image

    +

    10.3. Default overhead.

    -

    Or you can define also zones using trigger zones.

    +

    The default overhead is set to 1. That essentially means that there isn't any overhead set by default.

    -

    10.5. "Script it":

    +

    The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured.

    -

    Find the following mission script as an example:

    +

    Use the AIA2ADISPATCHER.SetDefaultOverhead() method can be used to set the default overhead or defense strength for ALL squadrons.

    -
       -- Define a SET_GROUP object that builds a collection of groups that define the EWR network.
    -   -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR.
    -   DetectionSetGroup = SET_GROUP:New()
    -   DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
    -   DetectionSetGroup:FilterStart()
    +

    10.4. Default grouping.

    - -- Here we define detection to be done by area, with a grouping radius of 3000. - Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) +

    The default grouping is set to one airplane. That essentially means that there won't be any grouping applied by default.

    - -- Setup the A2A dispatcher, and initialize it. - A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) +

    The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured.

    +

    Use the method AIA2ADISPATCHER.SetDefaultGrouping() to set the default grouping of spawned airplanes for all squadrons.

    +

    10.5. Default RTB fuel treshold.

    - -- Initialize the dispatcher, setting up a border zone. This is a polygon, - -- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area. - -- Any enemy crossing this border will be engaged. - CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) - A2ADispatcher:SetBorderZone( { CCCPBorderZone } ) +

    When an airplane gets out of fuel to a certain %-tage, which is 15% (0.15), it will go RTB, and will be replaced with a new airplane when applicable.

    - -- Initialize the dispatcher, setting up a radius of 100km where any airborne friendly - -- without an assignment within 100km radius from a detected target, will engage that target. - A2ADispatcher:SetEngageRadius( 300000 ) +

    Use the method AIA2ADISPATCHER.SetDefaultFuelThreshold() to set the default fuel treshold of spawned airplanes for all squadrons.

    - -- Setup the squadrons. - A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 ) - A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 ) - A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 ) - A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 ) - A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 ) +

    10.6. Default RTB damage treshold.

    - -- Setup the overhead - A2ADispatcher:SetSquadronOverhead( "Mineralnye", 1.2 ) - A2ADispatcher:SetSquadronOverhead( "Maykop", 1 ) - A2ADispatcher:SetSquadronOverhead( "Mozdok", 1.5 ) - A2ADispatcher:SetSquadronOverhead( "Sochi", 1 ) - A2ADispatcher:SetSquadronOverhead( "Novo", 1 ) +

    When an airplane is damaged to a certain %-tage, which is 40% (0.40), it will go RTB, and will be replaced with a new airplane when applicable.

    - -- Setup the Grouping - A2ADispatcher:SetSquadronGrouping( "Mineralnye", 2 ) - A2ADispatcher:SetSquadronGrouping( "Sochi", 2 ) - A2ADispatcher:SetSquadronGrouping( "Novo", 3 ) +

    Use the method AIA2ADISPATCHER.SetDefaultDamageThreshold() to set the default damage treshold of spawned airplanes for all squadrons.

    - -- Setup the Takeoff methods - A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air ) - A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) +

    10.7. Default settings for CAP.

    - -- Setup the Landing methods - A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway ) +

    10.7.1. Default CAP Time Interval.

    +

    CAP is time driven, and will evaluate in random time intervals if a new CAP needs to be spawned. +The default CAP time interval is between 180 and 600 seconds.

    - -- CAP Squadron execution. - CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) +

    Use the method AIA2ADISPATCHER.SetDefaultCapTimeInterval() to set the default CAP time interval of spawned airplanes for all squadrons.
    +Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the AIA2ADISPATCHER.SetSquadronCapTimeInterval() method.

    - CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) +

    10.7.2. Default CAP limit.

    - CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) +

    Multiple CAP can be airborne at the same time for one squadron, which is controlled by the CAP limit. +The default CAP limit is 1 CAP per squadron to be airborne at the same time. +Note that the default CAP limit is used when a Squadron CAP is defined, and cannot be changed afterwards. +So, ensure that you set the default CAP limit before you spawn the Squadron CAP.

    - -- GCI Squadron execution. - A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) +

    Use the method AIA2ADISPATCHER.SetDefaultCapTimeInterval() to set the default CAP time interval of spawned airplanes for all squadrons.
    +Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the AIA2ADISPATCHER.SetSquadronCapTimeInterval() method.

    + +

    10.7.3. Default tanker for refuelling when executing CAP.

    + +

    Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. +This greatly increases the efficiency of your CAP operations.

    + +

    In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. +Then, use the method AIA2ADISPATCHER.SetDefaultTanker() to set the tanker for the dispatcher. +Use the method AIA2ADISPATCHER.SetDefaultFuelTreshold() to set the %-tage left in the defender airplane tanks when a refuel action is needed.

    + +

    When the tanker specified is alive and in the air, the tanker will be used for refuelling.

    + +

    For example, the following setup will set the default refuel tanker to "Tanker":

    + +

    Banner Image

    + +
     -- Define the CAP
    + A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 )
    + A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 )
    + A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) 
    + A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 )
    +
    + -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left.
    + A2ADispatcher:SetDefaultFuelThreshold( 0.9 )
    + A2ADispatcher:SetDefaultTanker( "Tanker" )
     
    -

    10.5.1. Script the EWR network

    +

    10.8. Default settings for GCI.

    -

    Banner Image

    +

    10.8.1. Optimal intercept point calculation.

    -

    10.5.2. Script the AI_A2A_DISPATCHER object and configure it

    +

    When intruders are detected, the intrusion path of the attackers can be monitored by the EWR.
    +Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. +This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time.

    -

    Banner Image

    - -

    10.5.3. Script the squadrons

    - -

    Banner Image

    - -

    Create the squadrons using the AIA2ADISPATCHER.SetSquadron objects using:

    +

    Therefore, an optimal intercept point is calculated which takes a couple of parameters:

      -
    • Zone#ZONE class to create a zone using a trigger zone set in the mission editor.
    • -
    • Zone#ZONE_UNIT class to create a zone around a unit object.
    • -
    • Zone#ZONE_GROUP class to create a zone around a group object.
    • -
    • Zone#ZONE_POLYGON class to create a polygon zone using a late activated group object.
    • +
    • The average bearing of the intruders for an amount of seconds.
    • +
    • The average speed of the intruders for an amount of seconds.
    • +
    • An assumed time it takes to get planes operational at the airbase.
    -

    Use the @{#AIA2ADISPATCHER.SetSquadronCap)() method to define CAP execution for the squadron, within the CAP zone defined.

    +

    The intercept point will determine:

    -

    Banner Image

    +
      +
    • If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB.
    • +
    • The optimal airbase from where defenders will takeoff for GCI.
    • +
    -

    Use the @{#AIA2ADISPATCHER.SetSquadronCapInterval)() method to define how many CAP groups can be airborne at the same time, and the timing intervals.

    +

    Use the method AIA2ADISPATCHER.SetIntercept() to modify the assumed intercept delay time to calculate a valid interception.

    -

    Banner Image

    +

    10.8.2. Default Disengage Radius.

    + +

    The radius to disengage any target when the distance of the defender to the home base is larger than the specified meters. +The default Disengage Radius is 300km (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons!

    + +

    Use the method AIA2ADISPATCHER.SetDisengageRadius() to modify the default Disengage Radius to another distance setting.

    -

    Use the @{#AIA2ADISPATCHER.SetSquadronGci)() method to define GCI execution for the squadron.

    11. Q & A:

    @@ -1152,56 +1663,88 @@ Therefore if F4s are wanted as a coalition’s CAP or GCI aircraft Germany will
    - #AI_A2A_DISPATCHER_GCICAP - -AI_A2A_DISPATCHER_GCICAP + #AI_A2A_GCICAP + +AI_A2A_GCICAP
    -

    AI_A2A_DISPATCHER_GCICAP class, extends AI#AIA2ADISPATCHER

    +

    AI_A2A_GCICAP class, extends AIA2ADispatcher#AIA2ADISPATCHER

    Banner Image

    -

    The #AIA2ADISPATCHER class is designed to create an automatic air defence system for a coalition.

    +

    The AIA2AGCICAP class is designed to create an automatic air defence system for a coalition setting up GCI and CAP air defenses.

    +

    The class derives from AI#AIA2ADISPATCHER and thus, all the methods that are defined in the AI#AIA2ADISPATCHER class, can be used also in AI_A2A_GCICAP.

    +
    + +

    Demo Missions

    + +

    AI_A2A_GCICAP for Caucasus

    +

    AI_A2A_GCICAP for NTTR

    +

    AI_A2A_GCICAP for Normandy

    + +

    AI_A2A_GCICAP for beta testers

    + +
    + +

    YouTube Channel

    + +

    DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System

    + +

    Banner Image

    -

    It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. -CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept detected enemy aircraft or they run short of fuel and must return to base (RTB). When a CAP flight leaves their zone to perform an interception or return to base a new CAP flight will spawn to take their place. -If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. -With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. -In short it is a plug in very flexible and configurable air defence module for DCS World.

    +

    AI_A2A_GCICAP includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy +air movements that are detected by an airborne or ground based radar network.

    -

    Note that in order to create a two way A2A defense system, two AI_A2A_DISPATCHER_GCICAP defense system may need to be created, for each coalition one. -This is a good implementation, because maybe in the future, more coalitions may become available in DCS world.

    +

    With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system.

    -

    1. AI_A2A_DISPATCHER_GCICAP constructor:

    +

    The AIA2AGCICAP provides a lightweight configuration method using the mission editor. Within a very short time, and with very little coding, +the mission designer is able to configure a complete A2A defense system for a coalition using the DCS Mission Editor available functions. +Using the DCS Mission Editor, you define borders of the coalition which are guarded by GCICAP, +configure airbases to belong to the coalition, define squadrons flying certain types of planes or payloads per airbase, and define CAP zones. +Very little lua needs to be applied, a one liner, which is fully explained below, which can be embedded +right in a DO SCRIPT trigger action or in a larger DO SCRIPT FILE trigger action.

    -

    The AIA2ADISPATCHER_GCICAP.New() method creates a new AI_A2A_DISPATCHER_GCICAP instance. -There are two parameters required, a list of prefix group names that collects the groups of the EWR network, and a radius in meters, -that will be used to group the detected targets.

    +

    CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept +detected enemy aircraft or they run short of fuel and must return to base (RTB).

    -

    1.1. Define the EWR network:

    +

    When a CAP flight leaves their zone to perform a GCI or return to base a new CAP flight will spawn to take its place. +If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control.

    -

    As part of the AI_A2A_DISPATCHER_GCICAP constructor, a list of prefixes must be given of the group names defined within the mission editor, -that define the EWR network.

    +

    In short it is a plug in very flexible and configurable air defence module for DCS World.

    -

    An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.

    +
    -

    Banner Image

    +

    The following actions need to be followed when using AI_A2A_GCICAP in your mission:

    -

    Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. +

    1) Configure a working AI_A2A_GCICAP defense system for ONE coalition.

    + +

    1.1) Define which airbases are for which coalition.

    + +

    Mission Editor Action

    + +

    Color the airbases red or blue. You can do this by selecting the airbase on the map, and select the coalition blue or red.

    + +

    1.2) Place groups of units given a name starting with a EWR prefix of your choice to build your EWR network.

    + +

    Mission Editor Action

    + +

    All EWR groups starting with the EWR prefix (text) will be included in the detection system.

    + +

    An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. +Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). -Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. +Additionally, ANY other radar capable unit can be part of the EWR network! +Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. The position of these units is very important as they need to provide enough coverage to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.

    -

    Banner Image

    -

    Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. For example if they are a long way forward and can detect enemy planes on the ground and taking off they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. @@ -1212,29 +1755,196 @@ It all depends on what the desired effect is.

    EWR networks are dynamically maintained. By defining in a smart way the names or name prefixes of the groups with EWR capable units, these groups will be automatically added or deleted from the EWR network, increasing or decreasing the radar coverage of the Early Warning System.

    -

    See the following example to setup an EWR network containing EWR stations and AWACS.

    +

    1.3) Place Airplane or Helicopter Groups with late activation switched on above the airbases to define Squadrons.

    -
    -- Setup the A2A GCICAP dispatcher, and initialize it.
    -A2ADispatcher = AI_A2A_DISPATCHER_GCICAP:New( { "DF CCCP AWACS", "DF CCCP EWR" }, 30000 )
    -
    +

    Mission Editor Action

    -

    The above example creates a new AIA2ADISPATCHER_GCICAP instance, and stores this in the variable (object) A2ADispatcher. -The first parameter is are the prefixes of the group names that define the EWR network. -The A2A dispatcher will filter all active groups with a group name starting with DF CCCP AWACS or DF CCCP EWR to be included in the EWR network.

    +

    These are templates, with a given name starting with a Template prefix above each airbase that you wanna have a squadron. +These templates need to be within 1.5km from the airbase center. They don't need to have a slot at the airplane, they can just be positioned above the airbase, +without a route, and should only have ONE unit.

    -

    1.2. Define the detected target grouping radius:

    +

    Mission Editor Action

    -

    As a second parameter of the AIA2ADISPATCHER_GCICAP.New() method, a radius in meters must be given. The radius indicates that detected targets need to be grouped within a radius of 30km. +

    All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.

    + +

    1.4) Place floating helicopters to create the CAP zones defined by its route points.

    + +

    Mission Editor Action

    + +

    All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.

    + +

    The helicopter indicates the start of the CAP zone. +The route points define the form of the CAP zone polygon.

    + +

    Mission Editor Action

    + +

    The place of the helicopter is important, as the airbase closest to the helicopter will be the airbase from where the CAP planes will take off for CAP.

    + +

    2) There are a lot of defaults set, which can be further modified using the methods in AI#AIA2ADISPATCHER:

    + +

    2.1) Planes are taking off in the air from the airbases.

    + +

    This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiiing airplanes, +resulting in the airbase to halt operations.

    + +

    You can change the way how planes take off by using the inherited methods from AI_A2A_DISPATCHER:

    + + + +

    Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. +The more and the longer aircraft need to taxi at an airfield, the more risk there is that:

    + +
      +
    • aircraft will stop waiting for each other or for a landing aircraft before takeoff.
    • +
    • aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other.
    • +
    • aircraft may collide at the airbase.
    • +
    • aircraft may be awaiting the landing of a plane currently in the air, but never lands ...
    • +
    + +

    Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. +If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues!

    + +

    2.2) Planes return near the airbase or will land if damaged.

    + +

    When damaged airplanes return to the airbase, they will be routed and will dissapear in the air when they are near the airbase. +There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land.

    + +

    You can change the way how planes land by using the inherited methods from AI_A2A_DISPATCHER:

    + + + +

    You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. +When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the +A2A defense system, as no new CAP or GCI planes can takeoff. +Note that the method AIA2ADISPATCHER.SetSquadronLandingNearAirbase() will only work for returning aircraft, not for damaged or out of fuel aircraft. +Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control.

    + +

    2.3) CAP operations setup for specific airbases, will be executed with the following parameters:

    + +
      +
    • The altitude will range between 6000 and 10000 meters.
    • +
    • The CAP speed will vary between 500 and 800 km/h.
    • +
    • The engage speed between 800 and 1200 km/h.
    • +
    + +

    You can change or add a CAP zone by using the inherited methods from AI_A2A_DISPATCHER:

    + +

    The method AIA2ADISPATCHER.SetSquadronCap() defines a CAP execution for a squadron.

    + +

    Setting-up a CAP zone also requires specific parameters:

    + +
      +
    • The minimum and maximum altitude
    • +
    • The minimum speed and maximum patrol speed
    • +
    • The minimum and maximum engage speed
    • +
    • The type of altitude measurement
    • +
    + +

    These define how the squadron will perform the CAP while partrolling. Different terrain types requires different types of CAP.

    + +

    The AIA2ADISPATCHER.SetSquadronCapInterval() method specifies how much and when CAP flights will takeoff.

    + +

    It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system.

    + +

    For example, the following setup will create a CAP for squadron "Sochi":

    + +

    A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 )

    + +

    2.4) Each airbase will perform GCI when required, with the following parameters:

    + +
      +
    • The engage speed is between 800 and 1200 km/h.
    • +
    + +

    You can change or add a GCI parameters by using the inherited methods from AI_A2A_DISPATCHER:

    + +

    The method AIA2ADISPATCHER.SetSquadronGci() defines a GCI execution for a squadron.

    + +

    Setting-up a GCI readiness also requires specific parameters:

    + +
      +
    • The minimum speed and maximum patrol speed
    • +
    + +

    Essentially this controls how many flights of GCI aircraft can be active at any time. +Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. +GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, +too short will mean that the intruders may have alraedy passed the ideal interception point!

    + +

    For example, the following setup will create a GCI for squadron "Sochi":

    + +

    A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 )

    + +

    2.5) Grouping or detected targets.

    + +

    Detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate +group being detected.

    + +

    Targets will be grouped within a radius of 30km by default.

    + +

    The radius indicates that detected targets need to be grouped within a radius of 30km. The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. Fast planes like in the 80s, need a larger radius than WWII planes.
    Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft.

    -

    Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate -group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!

    +

    3) Additional notes:

    -

    2. AIA2ADISPATCHER_DOCUMENTATION is derived from #AIA2ADISPATCHER,

    -

    so all further documentation needs to be consulted in this class -for documentation consistency.

    +

    In order to create a two way A2A defense system, two AI_A2A_GCICAP defense systems must need to be created, for each coalition one. +Each defense system needs its own EWR network setup, airplane templates and CAP configurations.

    + +

    This is a good implementation, because maybe in the future, more coalitions may become available in DCS world.

    + +

    4) Coding examples how to use the AI_A2A_GCICAP class:

    + +

    4.1) An easy setup:

    + +
     -- Setup the AI_A2A_GCICAP dispatcher for one coalition, and initialize it.
    + GCI_Red = AI_A2A_GCICAP:New( "EWR CCCP", "SQUADRON CCCP", "CAP CCCP", 2 )
    +
    +

    -- +The following parameters were given to the :New method of AIA2AGCICAP, and mean the following:

    + +
      +
    • "EWR CCCP": Groups of the blue coalition are placed that define the EWR network. These groups start with the name EWR CCCP.
    • +
    • "SQUADRON CCCP": Late activated Groups objects of the red coalition are placed above the relevant airbases that will contain these templates in the squadron. + These late activated Groups start with the name SQUADRON CCCP. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level.
    • +
    • "CAP CCCP": CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. + These Helicopter Group objects start with the name CAP CCCP, and will be the locations wherein CAP will be performed.
    • +
    • 2 Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously.
    • +
    + + +

    4.2) A more advanced setup:

    + +
     -- Setup the AI_A2A_GCICAP dispatcher for the blue coalition.
    +
    + A2A_GCICAP_Blue = AI_A2A_GCICAP:New( { "BLUE EWR" }, { "104th", "105th", "106th" }, { "104th CAP" }, 4 ) 
    +
    + +

    The following parameters for the :New method have the following meaning:

    + +
      +
    • { "BLUE EWR" }: An array of the group name prefixes of the groups of the blue coalition are placed that define the EWR network. These groups start with the name BLUE EWR.
    • +
    • { "104th", "105th", "106th" }: An array of the group name prefixes of the Late activated Groups objects of the blue coalition are + placed above the relevant airbases that will contain these templates in the squadron. + These late activated Groups start with the name 104th or 105th or 106th.
    • +
    • { "104th CAP" }: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, + where the route points define the route of the polygon of the CAP Zone. + These Helicopter Group objects start with the name 104th CAP, and will be the locations wherein CAP will be performed.
    • +
    • 4 Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously.
    • +
    @@ -1250,7 +1960,7 @@ for documentation consistency.

    -AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron, Defender) +AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron, Defender, Size)
    @@ -1268,6 +1978,11 @@ for documentation consistency.

    Defender :

    + +
  • + +

    Size :

    +
  • @@ -1406,7 +2121,7 @@ DefenderSquadron

    -AI_A2A_DISPATCHER:CountDefendersEngaged(Target) +AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection)
    @@ -1417,7 +2132,7 @@ DefenderSquadron

    • -

      Target :

      +

      AttackerDetection :

    @@ -1427,7 +2142,7 @@ DefenderSquadron

    -AI_A2A_DISPATCHER:CountDefendersToBeEngaged(DetectedItem, DefenderCount) +AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection, DefenderCount)
    @@ -1438,7 +2153,7 @@ DefenderSquadron

    • -

      DetectedItem :

      +

      AttackerDetection :

    • @@ -1447,6 +2162,23 @@ DefenderSquadron

    +
    +
    +
    +
    + + + +AI_A2A_DISPATCHER.DefenderDefault + +
    +
    + + + + +

    The Defender Default Settings over all Squadrons.

    +
    @@ -1522,6 +2254,19 @@ DefenderSquadron

    + +
    +
    +
    + + +AI_A2A_DISPATCHER.DisengageRadius + +
    +
    + + +
    @@ -1577,24 +2322,19 @@ If there are no targets to be set.

    -AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem, Target) +AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem)

    Creates an GCI task when there are targets for it.

    -

    Parameters

    +

    Parameter

    Return values

    @@ -1727,6 +2467,66 @@ The squadron name.

    #AIA2ADISPATCHER:

    +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:GetDefaultLanding() + +
    +
    + +

    Gets the default method at which flights will land and despawn as part of the defense system.

    + +

    Return value

    + +

    #number: +Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown

    + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default despawn near the airbase when returning.
    +  local LandingMethod = A2ADispatcher:GetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase )
    +  if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then
    +   ...
    +  end
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:GetDefaultTakeoff() + +
    +
    + +

    Gets the default method at which new flights will spawn and take-off as part of the defense system.

    + +

    Return value

    + +

    #number: +Takeoff From the airbase hot, from the airbase cold, in the air, from the runway.

    + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default take-off in the air.
    +  local TakeoffMethod = A2ADispatcher:GetDefaultTakeoff()
    +  if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then
    +    ...
    +  end
    +  
    +
    @@ -1761,6 +2561,27 @@ The squadron name.

    +

    Parameter

    +
      +
    • + +

      Defender :

      + +
    • +
    + +
    +
    +
    + + +AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender) + +
    +
    + + +

    Parameter

    • @@ -1937,10 +2758,10 @@ Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown<

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
      -  -- Let new flights take-off in the air.
      -  local LandingMethod = Dispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase )
      +  -- Let new flights despawn near the airbase when returning.
      +  local LandingMethod = A2ADispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase )
         if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then
          ...
         end
      @@ -1976,10 +2797,10 @@ Takeoff From the airbase hot, from the airbase cold, in the air, from the runway
       			

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
         -- Let new flights take-off in the air.
      -  local TakeoffMethod = Dispatcher:GetSquadronTakeoff( "SquadronName" )
      +  local TakeoffMethod = A2ADispatcher:GetSquadronTakeoff( "SquadronName" )
         if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then
           ...
         end
      @@ -2005,27 +2826,25 @@ Takeoff From the airbase hot, from the airbase cold, in the air, from the runway
       
      -AI_A2A_DISPATCHER:New(Detection, GroupingRadius) +AI_A2A_DISPATCHER:New(Detection)

      AIA2ADISPATCHER constructor.

      -

      Parameters

      + +

      This is defining the A2A DISPATCHER for one coaliton. +The Dispatcher works with a Functional#Detection object that is taking of the detection of targets using the EWR units. +The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently.

      + +

      Parameter

      • Functional.Detection#DETECTION_BASE Detection : The DETECTION object that will detects targets using the the Early Warning Radar network.

        -
      • -
      • - -

        #number GroupingRadius : -The radius in meters wherein detected planes are being grouped as one target area. -For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter.

        -

      Return value

      @@ -2035,8 +2854,18 @@ self

      Usage:

        
      -  -- Set a new AI A2A Dispatcher object, based on an EWR network with a 6 km grouping radius.
      +  -- Setup the Detection, using DETECTION_AREAS.
      +  -- First define the SET of GROUPs that are defining the EWR network.
      +  -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
      +  DetectionSetGroup = SET_GROUP:New()
      +  DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
      +  DetectionSetGroup:FilterStart()
         
      +  -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
      +  Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
      +
      +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
      +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  --   
       
      @@ -2461,7 +3290,7 @@ If a hot war is chosen then no borders actually need to be defi
    • Core.Zone#ZONE_BASE BorderZone : -An object derived from ZONE_BASE, that defines a zone between

      +An object derived from ZONEBASE, or a list of objects derived from ZONEBASE.

    @@ -2472,9 +3301,20 @@ An object derived from ZONE_BASE, that defines a zone between

    Usage:

    
    -  -- Set a polygon zone as the border for the A2A dispatcher.
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Set one ZONE_POLYGON object as the border for the A2A dispatcher.
       local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit.
    -  Dispatcher:SetBorderZone( BorderZone )
    +  A2ADispatcher:SetBorderZone( BorderZone )
    +  
    +or
    +  
    +  -- Set two ZONE_POLYGON objects as the border for the A2A dispatcher.
    +  local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit.
    +  local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit.
    +  A2ADispatcher:SetBorderZone( { BorderZone1, BorderZone2 } )
    +  
       
    @@ -2482,8 +3322,618 @@ An object derived from ZONE_BASE, that defines a zone between

    + +AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit) + +
    +
    + +

    Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron.

    + + +

    The default CAP limit is 1 CAP, which means one CAP group being spawned.

    + +

    Parameter

    +
      +
    • + +

      #number CapLimit : +The maximum amount of CAP that can be airborne at the same time for the squadron.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Now Setup the default CAP limit.
    +  A2ADispatcher:SetDefaultCapLimit( 2 ) -- Maximum 2 CAP per squadron.
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds, CapMaxSeconds) + +
    +
    + +

    Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing.

    + + +

    The default CAP time interval is between 180 and 600 seconds.

    + +

    Parameters

    +
      +
    • + +

      #number CapMinSeconds : +The minimum amount of seconds for the random time interval.

      + +
    • +
    • + +

      #number CapMaxSeconds : +The maximum amount of seconds for the random time interval.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Now Setup the default CAP time interval.
    +  A2ADispatcher:SetDefaultCapTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds.
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold) + +
    +
    + +

    Set the default damage treshold when defenders will RTB.

    + + +

    The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.

    + +

    Parameter

    +
      +
    • + +

      #number DamageThreshold : +A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Now Setup the default damage treshold.
    +  A2ADispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) + +
    +
    + +

    Set the default fuel treshold when defenders will RTB or Refuel in the air.

    + + +

    The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.

    + +

    Parameter

    +
      +
    • + +

      #number FuelThreshold : +A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Now Setup the default fuel treshold.
    +  A2ADispatcher:SetDefaultRefuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping) + +
    +
    + +

    Sets the default grouping of new airplanes spawned.

    + + +

    Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense.

    + +

    Parameter

    +
      +
    • + +

      #number Grouping : +The level of grouping that will be applied of the CAP or GCI defenders.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Set a grouping by default per 2 airplanes.
    +  A2ADispatcher:SetDefaultGrouping( 2 )
    +
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultLanding(Landing) + +
    +
    + +

    Defines the default method at which flights will land and despawn as part of the defense system.

    + +

    Parameter

    +
      +
    • + +

      #number Landing : +The landing method which can be NearAirbase, AtRunway, AtEngineShutdown

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default despawn near the airbase when returning.
    +  A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase )
    +  
    +  -- Let new flights by default despawn after landing land at the runway.
    +  A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtRunway )
    +  
    +  -- Let new flights by default despawn after landing and parking, and after engine shutdown.
    +  A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown )
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() + +
    +
    + +

    Sets flights by default to land and despawn at engine shutdown, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let flights by default land and despawn at engine shutdown.
    +  A2ADispatcher:SetDefaultLandingAtEngineShutdown()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() + +
    +
    + +

    Sets flights by default to land and despawn at the runway, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let flights by default land at the runway and despawn.
    +  A2ADispatcher:SetDefaultLandingAtRunway()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() + +
    +
    + +

    Sets flights by default to land and despawn near the airbase in the air, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let flights by default to land near the airbase and despawn.
    +  A2ADispatcher:SetDefaultLandingNearAirbase()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead) + +
    +
    + +

    Defines the default amount of extra planes that will take-off as part of the defense system.

    + +

    Parameter

    +
      +
    • + +

      #number Overhead : +The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. +The default overhead is 1, so equal balance. The AIA2ADISPATCHER.SetOverhead() method can be used to tweak the defense strength, +taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... +So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. +The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values:

      + +
        +
      • Higher than 1, will increase the defense unit amounts.
      • +
      • Lower than 1, will decrease the defense unit amounts.
      • +
      + +

      The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group +multiplied by the Overhead and rounded up to the smallest integer.

      + +

      The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution.

      + +

      See example below. +

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2.
    +  -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 =>  rounded up gives 3.
    +  -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes.
    +  -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6  => rounded up gives 6 planes.
    +  
    +  A2ADispatcher:SetDefaultOverhead( 1.5 )
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff) + +
    +
    + +

    Defines the default method at which new flights will spawn and take-off as part of the defense system.

    + +

    Parameter

    +
      +
    • + +

      #number Takeoff : +From the airbase hot, from the airbase cold, in the air, from the runway.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default take-off in the air.
    +  A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Air )
    +  
    +  -- Let new flights by default take-off from the runway.
    +  A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Runway )
    +  
    +  -- Let new flights by default take-off from the airbase hot.
    +  A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Hot )
    +
    +  -- Let new flights by default take-off from the airbase cold.
    +  A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold )
    +
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() + +
    +
    + +

    Sets flights to by default take-off from the airbase at a cold location, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights take-off from a cold parking spot.
    +  A2ADispatcher:SetDefaultTakeoffFromParkingCold()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() + +
    +
    + +

    Sets flights by default to take-off from the airbase at a hot location, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default take-off at a hot parking spot.
    +  A2ADispatcher:SetDefaultTakeoffFromParkingHot()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() + +
    +
    + +

    Sets flights by default to take-off from the runway, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default take-off from the runway.
    +  A2ADispatcher:SetDefaultTakeoffFromRunway()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() + +
    +
    + +

    Sets flights to default take-off in the air, as part of the defense system.

    + +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Let new flights by default take-off in the air.
    +  A2ADispatcher:SetDefaultTakeoffInAir()
    +  
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) + +
    +
    + +

    Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.

    + +

    Parameter

    +
      +
    • + +

      #number TakeoffAltitude : +The altitude in meters above the ground.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +
    +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
    +  
    +  -- Set the default takeoff altitude when taking off in the air.
    +  A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 )  -- This makes planes start at 2000 meters above the ground.
    +
    + +
    +
    +
    +
    + + +AI_A2A_DISPATCHER:SetDefaultTanker(TankerName) + +
    +
    + +

    Set the default tanker where defenders will Refuel in the air.

    + +

    Parameter

    +
      +
    • + +

      #strig TankerName : +A string defining the group name of the Tanker as defined within the Mission Editor.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
    +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
    +  
    +  -- Now Setup the default fuel treshold.
    +  A2ADispatcher:SetDefaultRefuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
    +  
    +  -- Now Setup the default tanker.
    +  A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor.
    + +
    +
    +
    +
    + -AI_A2A_DISPATCHER:SetDefenderTask(Defender, Type, Fsm, Target) +AI_A2A_DISPATCHER:SetDefenderTask(SquadronName, Defender, Type, Fsm, Target)
    @@ -2494,6 +3944,11 @@ An object derived from ZONE_BASE, that defines a zone between

    @@ -2549,6 +4004,42 @@ An object derived from ZONE_BASE, that defines a zone between

    + +AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius) + +
    +
    + +

    Define the radius to disengage any target when the distance to the home base is larger than the specified meters.

    + +

    Parameter

    +
      +
    • + +

      #number DisengageRadius : +(Optional, Default = 300000) The radius to disengage a target when too far from the home base.

      + +
    • +
    +

    Return value

    + +

    #AIA2ADISPATCHER:

    + + +

    Usage:

    +
    
    +  -- Set 50km as the Disengage Radius.
    +  A2ADispatcher:SetDisengageRadius( 50000 )
    +  
    +  -- Set 100km as the Disengage Radius.
    +  A2ADispatcher:SetDisngageRadius() -- 300000 is the default value.
    +  
    + +
    +
    +
    +
    + AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius) @@ -2558,14 +4049,23 @@ An object derived from ZONE_BASE, that defines a zone between

    Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.

    -

    So, if there is a target area detected and reported, -then any friendlies that are airborne near this target area, -will be commanded to (re-)engage that target when available (if no other tasks were commanded). -For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -will be considered to receive the command to engage that target area. -You need to evaluate the value of this parameter carefully. -If too small, more intercept missions may be triggered upon detected target areas. -If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.

    +

    If there is a target area detected and reported, then any friendlies that are airborne near this target area, +will be commanded to (re-)engage that target when available (if no other tasks were commanded).

    + +

    For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, +will be considered to receive the command to engage that target area.

    + +

    You need to evaluate the value of this parameter carefully:

    + +
      +
    • If too small, more intercept missions may be triggered upon detected target areas.
    • +
    • If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
    • +
    + +

    **Use the method AIA2ADISPATCHER.SetEngageRadius() to modify the default Engage Radius for ALL squadrons.**

    + +

    Demonstration Mission: AID-019 - AI_A2A - Engage Range Test

    +

    Parameter

      @@ -2584,10 +4084,10 @@ If too large, any airborne cap may not be able to reach the detected target area

      Usage:

      
         -- Set 50km as the radius to engage any target by airborne friendlies.
      -  Dispatcher:SetEngageRadius( 50000 )
      +  A2ADispatcher:SetEngageRadius( 50000 )
         
         -- Set 100km as the radius to engage any target by airborne friendlies.
      -  Dispatcher:SetEngageRadius() -- 100000 is the default value.
      +  A2ADispatcher:SetEngageRadius() -- 100000 is the default value.
         
    @@ -2604,14 +4104,18 @@ If too large, any airborne cap may not be able to reach the detected target area

    Define the radius to check if a target can be engaged by an ground controlled intercept.

    -

    So, if there is a target area detected and reported, -and a GCI is to be executed, -then it will be check if the target is within the GCI from the nearest airbase. -For example, if 150000 is given as a value, then any airbase within 150km from the detected target, -will be considered to receive the command to GCI. -You need to evaluate the value of this parameter carefully. -If too small, intercept missions may be triggered too late. -If too large, intercept missions may be triggered when the detected target is too far.

    +

    When targets are detected that are still really far off, you don't want the AIA2ADISPATCHER to launch intercepts just yet. +You want it to wait until a certain Gci range is reached, which is the distance of the closest airbase to target +being smaller than the Ground Controlled Intercept radius or Gci radius.

    + +

    The default Gci radius is defined as 200000 or 200km. Override the default Gci radius when the era of the warfare is early, or, +when you don't want to let the AIA2ADISPATCHER react immediately when a certain border or area is not being crossed.

    + +

    Use the method AIA2ADISPATCHER.SetGciRadius() to set a specific controlled ground intercept radius. +The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.

    + +

    Demonstration Mission: AID-013 - AI_A2A - Intercept Test

    +

    Parameter

      @@ -2629,11 +4133,14 @@ If too large, intercept missions may be triggered when the detected target is to

      Usage:

      
      +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
      +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) 
      +  
         -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase.
      -  Dispatcher:SetGciRadius( 100000 )
      +  A2ADispatcher:SetGciRadius( 100000 )
         
         -- Set 200km as the radius to ground control intercept.
      -  Dispatcher:SetGciRadius() -- 200000 is the default value.
      +  A2ADispatcher:SetGciRadius() -- 200000 is the default value.
         
      @@ -2641,34 +4148,103 @@ If too large, intercept missions may be triggered when the detected target is to
      - -AI_A2A_DISPATCHER:SetSquadron(SquadronName, AirbaseName, SpawnTemplates, Resources) + +AI_A2A_DISPATCHER:SetIntercept(InterceptDelay)
      +

      Parameter

      +
        +
      • + +

        InterceptDelay :

        + +
      • +
      +
      +
      +
      +
      + + +AI_A2A_DISPATCHER:SetSquadron(SquadronName, AirbaseName, TemplatePrefixes, Resources) + +
      +
      + +

      This is the main method to define Squadrons programmatically.

      + + +

      Squadrons:

      + +
        +
      • Have a name or key that is the identifier or key of the squadron.
      • +
      • Have specific plane types defined by templates.
      • +
      • Are located at one specific airbase. Multiple squadrons can be located at one airbase through.
      • +
      • Optionally have a limited set of resources. The default is that squadrons have unlimited resources.
      • +
      + +

      The name of the squadron given acts as the squadron key in the AI_A2A_DISPATCHER:Squadron...() methods.

      + +

      Additionally, squadrons have specific configuration options to:

      + +
        +
      • Control how new aircraft are taking off from the airfield (in the air, cold, hot, at the runway).
      • +
      • Control how returning aircraft are landing at the airfield (in the air near the airbase, after landing, after engine shutdown).
      • +
      • Control the grouping of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped.
      • +
      • Control the overhead or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned.
      • +
      + +

      For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft.

      + +

      Parameters

      • -

        SquadronName :

        +

        #string SquadronName : +A string (text) that defines the squadron identifier or the key of the Squadron. +It can be any name, for example "104th Squadron" or "SQ SQUADRON1", whatever. +As long as you remember that this name becomes the identifier of your squadron you have defined. +You need to use this name in other methods too!

        +
      • -

        AirbaseName :

        +

        #string AirbaseName : +The airbase name where you want to have the squadron located. +You need to specify here EXACTLY the name of the airbase as you see it in the mission editor. +Examples are "Batumi" or "Tbilisi-Lochini". +EXACTLY the airbase name, between quotes "". +To ease the airbase naming when using the LDT editor and IntelliSense, the Airbase#AIRBASE class contains enumerations of the airbases of each map.

        + + +
      • -

        SpawnTemplates :

        +

        #string TemplatePrefixes : +A string or an array of strings specifying the prefix names of the templates (not going to explain what is templates here again). +Examples are { "104th", "105th" } or "104th" or "Template 1" or "BLUE PLANES". +Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. +If you have only one prefix name for a squadron, you don't need to use the { }, otherwise you need to use the brackets.

        +
      • -

        Resources :

        +

        #number Resources : +(optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available.

        +
      @@ -2677,6 +4253,31 @@ If too large, intercept missions may be triggered when the detected target is to

      #AIA2ADISPATCHER:

      +

      Usages:

      +
        +
      •   -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
        +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
        +  
      • +
      •   -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock...  
        +  A2ADispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 )
        +  
      • +
      •   -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock...
        +  -- Note that in this implementation, the A2A dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses.
        +  -- Note the usage of the {} for the airplane templates list.
        +  A2ADispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 )
        +  
      • +
      •   -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock...
        +  A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 )
        +  A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 )
        +  
      • +
      •   -- This is an example like the previous, but now with infinite resources.
        +  -- The Resources parameter is not given in the SetSquadron method.
        +  A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" )
        +  A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" )
        +  
        +  
      • +
      +
      @@ -2688,7 +4289,7 @@ If too large, intercept missions may be triggered when the detected target is to
      - +

      Set a CAP for a Squadron.

      Parameters

        @@ -2779,7 +4380,10 @@ The altitude type, which is a string "BARO" defining Barometric or "RADIO" defin
        +

        Set the squadron CAP parameters.

        + +

        Parameters

          @@ -2791,22 +4395,26 @@ The squadron name.

        • -

          CapLimit :

          +

          #number CapLimit : +(optional) The maximum amount of CAP groups to be spawned. Note that a CAP is a group, so can consist out of 1 to 4 airplanes. The default is 1 CAP group.

        • -

          LowInterval :

          +

          #number LowInterval : +(optional) The minimum time boundary in seconds when a new CAP will be spawned. The default is 180 seconds.

        • -

          HighInterval :

          +

          #number HighInterval : +(optional) The maximum time boundary in seconds when a new CAP will be spawned. The default is 600 seconds.

        • -

          Probability :

          +

          #number Probability : +Is not in use, you can skip this parameter.

        @@ -2836,6 +4444,51 @@ The squadron name.

        + +AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName, FuelThreshold) + +
        +
        + +

        Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.

        + + +

        The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.

        + +

        Parameters

        +
          +
        • + +

          #string SquadronName : +The name of the squadron.

          + +
        • +
        • + +

          #number FuelThreshold : +A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.

          + +
        • +
        +

        Return value

        + +

        #AIA2ADISPATCHER:

        + + +

        Usage:

        +
        
        +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
        +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
        +  
        +  -- Now Setup the default fuel treshold.
        +  A2ADispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
        +  
        + +
        +
        +
        +
        + AI_A2A_DISPATCHER:SetSquadronGci(SquadronName, EngageMinSpeed, EngageMaxSpeed) @@ -2890,7 +4543,10 @@ The maximum speed at which the gci can be executed.

        +

        Sets the grouping of new airplanes spawned.

        + +

        Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense.

        Parameters

          @@ -2915,8 +4571,10 @@ The level of grouping that will be applied of the CAP or GCI defenders.

          Usage:

          
           
          -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
          -  Dispatcher:SetSquadronGrouping( "SquadronName", 2 )
          +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
          +  
          +  -- Set a grouping per 2 airplanes.
          +  A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 )
           
           
          @@ -2956,16 +4614,16 @@ The landing method which can be NearAirbase, AtRunway, AtEngineShutdown

          Usage:

          
           
          -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
          +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
             
          -  -- Let new flights take-off in the air.
          -  Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase )
          +  -- Let new flights despawn near the airbase when returning.
          +  A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase )
             
          -  -- Let new flights take-off from the runway.
          -  Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway )
          +  -- Let new flights despawn after landing land at the runway.
          +  A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway )
             
          -  -- Let new flights take-off from the airbase hot.
          -  Dispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown )
          +  -- Let new flights despawn after landing and parking, and after engine shutdown.
          +  A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown )
           
        @@ -2998,10 +4656,10 @@ The name of the squadron.

        Usage:

        
         
        -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
        +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
           
           -- Let flights land and despawn at engine shutdown.
        -  Dispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" )
        +  A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" )
           
        @@ -3034,10 +4692,10 @@ The name of the squadron.

        Usage:

        
         
        -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
        +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
           
           -- Let flights land at the runway and despawn.
        -  Dispatcher:SetSquadronLandingAtRunway( "SquadronName" )
        +  A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" )
           
      @@ -3070,10 +4728,10 @@ The name of the squadron.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
      -  -- Let flights land in the air and despawn.
      -  Dispatcher:SetSquadronLandingNearAirbase( "SquadronName" )
      +  -- Let flights to land near the airbase and despawn.
      +  A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" )
         
      @@ -3129,15 +4787,14 @@ multiplied by the Overhead and rounded up to the smallest integer.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
         -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2.
         -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 =>  rounded up gives 3.
         -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes.
         -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6  => rounded up gives 6 planes.
         
      -  Dispatcher:SetSquadronOverhead( 1,5 )
      -
      +  A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 )
       
      @@ -3176,19 +4833,19 @@ From the airbase hot, from the airbase cold, in the air, from the runway.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
         -- Let new flights take-off in the air.
      -  Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air )
      +  A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air )
         
         -- Let new flights take-off from the runway.
      -  Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway )
      +  A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway )
         
         -- Let new flights take-off from the airbase hot.
      -  Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot )
      +  A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot )
       
         -- Let new flights take-off from the airbase cold.
      -  Dispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold )
      +  A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold )
       
       
      @@ -3222,10 +4879,10 @@ The name of the squadron.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
      -  -- Let new flights take-off in the air.
      -  Dispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" )
      +  -- Let new flights take-off from a cold parking spot.
      +  A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" )
         
      @@ -3258,10 +4915,10 @@ The name of the squadron.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
         -- Let new flights take-off in the air.
      -  Dispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" )
      +  A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" )
         
      @@ -3294,10 +4951,10 @@ The name of the squadron.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
      -  -- Let new flights take-off in the air.
      -  Dispatcher:SetSquadronTakeoffFromRunway( "SquadronName" )
      +  -- Let new flights take-off from the runway.
      +  A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" )
         
      @@ -3306,20 +4963,26 @@ The name of the squadron.

      -AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName) +AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName, TakeoffAltitude)

      Sets flights to take-off in the air, as part of the defense system.

      -

      Parameter

      +

      Parameters

      • #string SquadronName : The name of the squadron.

        +
      • +
      • + +

        #number TakeoffAltitude : +(optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used.

        +

      Return value

      @@ -3330,10 +4993,10 @@ The name of the squadron.

      Usage:

      
       
      -  local Dispatcher = AI_A2A_DISPATCHER:New( ... )
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
         
         -- Let new flights take-off in the air.
      -  Dispatcher:SetSquadronTakeoffInAir( "SquadronName" )
      +  A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" )
         
      @@ -3341,6 +5004,92 @@ The name of the squadron.

      + +AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName, TakeoffAltitude) + +
      +
      + +

      Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.

      + +

      Parameters

      +
        +
      • + +

        #string SquadronName : +The name of the squadron.

        + +
      • +
      • + +

        #number TakeoffAltitude : +The altitude in meters above the ground.

        + +
      • +
      +

      Return value

      + +

      #AIA2ADISPATCHER:

      + + +

      Usage:

      +
      
      +
      +  local A2ADispatcher = AI_A2A_DISPATCHER:New( ... )
      +  
      +  -- Set the default takeoff altitude when taking off in the air.
      +  A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground.
      +  
      + +
      +
      +
      +
      + + +AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName, TankerName) + +
      +
      + +

      Set the squadron tanker where defenders will Refuel in the air.

      + +

      Parameters

      +
        +
      • + +

        #string SquadronName : +The name of the squadron.

        + +
      • +
      • + +

        #strig TankerName : +A string defining the group name of the Tanker as defined within the Mission Editor.

        + +
      • +
      +

      Return value

      + +

      #AIA2ADISPATCHER:

      + + +

      Usage:

      +
      
      +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
      +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
      +  
      +  -- Now Setup the squadron fuel treshold.
      +  A2ADispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
      +  
      +  -- Now Setup the squadron tanker.
      +  A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor.
      + +
      +
      +
      +
      + AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay) @@ -3369,6 +5118,15 @@ Provide a value of true to display every 30 seconds a tactical

      #AIA2ADISPATCHER:

      +

      Usage:

      +
      
      +  -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
      +  A2ADispatcher = AI_A2A_DISPATCHER:New( Detection )  
      +  
      +  -- Now Setup the Tactical Display for debug mode.
      +  A2ADispatcher:SetTacticalDisplay( true )
      +  
      +
      @@ -3502,7 +5260,7 @@ Provide a value of true to display every 30 seconds a tactical
      -AI_A2A_DISPATCHER:onafterENGAGE(From, Event, To, Target, AIGroups) +AI_A2A_DISPATCHER:onafterENGAGE(From, Event, To, AttackerDetection, Defenders)
      @@ -3528,12 +5286,12 @@ Provide a value of true to display every 30 seconds a tactical
    • -

      Target :

      +

      AttackerDetection :

    • -

      AIGroups :

      +

      Defenders :

    @@ -3543,7 +5301,7 @@ Provide a value of true to display every 30 seconds a tactical
    -AI_A2A_DISPATCHER:onafterGCI(From, Event, To, Target, DefendersMissing, AIGroups) +AI_A2A_DISPATCHER:onafterGCI(From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies)
    @@ -3569,7 +5327,7 @@ Provide a value of true to display every 30 seconds a tactical
  • -

    Target :

    +

    AttackerDetection :

  • @@ -3579,7 +5337,7 @@ Provide a value of true to display every 30 seconds a tactical
  • -

    AIGroups :

    +

    DefenderFriendlies :

  • @@ -3591,28 +5349,57 @@ Provide a value of true to display every 30 seconds a tactical

    Enumerator for spawns at airbases

    -

    Type AI_A2A_DISPATCHER_GCICAP

    - -

    AIA2ADISPATCHER_GCICAP class.

    - -

    Field(s)

    +

    Type AI_A2A_GCICAP

    +

    Field(s)

    - -AI_A2A_DISPATCHER_GCICAP:New(<, GroupingRadius, EWRPrefixes) + + +AI_A2A_GCICAP.CAPTemplates
    -

    AIA2ADISPATCHER_GCICAP constructor.

    + + +
    +
    +
    +
    + + +AI_A2A_GCICAP:New(EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources) + +
    +
    + +

    AIA2AGCICAP constructor.

    Parameters

    • -

      #list < : -string> EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network.

      +

      #string EWRPrefixes : +A list of prefixes that of groups that setup the Early Warning Radar network.

      + +
    • +
    • + +

      #string TemplatePrefixes : +A list of template prefixes.

      + +
    • +
    • + +

      #string CapPrefixes : +A list of CAP zone prefixes (polygon zones).

      + +
    • +
    • + +

      #number CapLimit : +A number of how many CAP maximum will be spawned.

    • @@ -3624,28 +5411,276 @@ For airplanes, 6000 (6km) is recommended, and is also the default value of this
    • -

      EWRPrefixes :

      +

      #number EngageRadius : +The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task.

      + +
    • +
    • + +

      #number GciRadius : +The radius in meters wherein detected airplanes will GCI.

      + +
    • +
    • + +

      #number Resources : +The amount of resources that will be allocated to each squadron.

    Return value

    -

    #AIA2ADISPATCHER_GCICAP:

    +

    #AIA2AGCICAP:

    -

    Usage:

    -
      
    -  -- Set a new AI A2A Dispatcher object, based on an EWR network with a 30 km grouping radius
    -  -- This for ground and awacs installations.
    -  
    -  A2ADispatcher = AI_A2A_DISPATCHER_GCICAP:New( { "BlueEWRGroundRadars", "BlueEWRAwacs" }, 30000 )
    -  
    +

    Usages:

    +
      +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is DF CCCP. All groups starting with DF CCCP will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +  -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement.
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +  -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement.
      +  -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000, 30 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is nil. No CAP is created.
      +  -- The CAP Limit is nil.
      +  -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task.
      +  -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement.
      +  -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 )  
      +  
    • +
    + +
    +
    +
    +
    + + +AI_A2A_GCICAP:NewWithBorder(EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources) + +
    +
    + +

    AIA2AGCICAP constructor with border.

    + +

    Parameters

    +
      +
    • + +

      #string EWRPrefixes : +A list of prefixes that of groups that setup the Early Warning Radar network.

      + +
    • +
    • + +

      #string TemplatePrefixes : +A list of template prefixes.

      + +
    • +
    • + +

      #string BorderPrefix : +A Border Zone Prefix.

      + +
    • +
    • + +

      #string CapPrefixes : +A list of CAP zone prefixes (polygon zones).

      + +
    • +
    • + +

      #number CapLimit : +A number of how many CAP maximum will be spawned.

      + +
    • +
    • + +

      #number GroupingRadius : +The radius in meters wherein detected planes are being grouped as one target area. +For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter.

      + +
    • +
    • + +

      #number EngageRadius : +The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task.

      + +
    • +
    • + +

      #number GciRadius : +The radius in meters wherein detected airplanes will GCI.

      + +
    • +
    • + +

      #number Resources : +The amount of resources that will be allocated to each squadron.

      + +
    • +
    +

    Return value

    + +

    #AIA2AGCICAP:

    + + +

    Usages:

    +
      +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +  -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border.
      +  -- The CAP Zone prefix is "CAP Zone".
      +  -- The CAP Limit is 2.
      +  -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, 
      +  -- will be considered a defense task if the target is within 60km from the defender.
      +  -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement.
      +  -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000, 30 )  
      +  
    • +
    •   
      +  -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources.
      +  -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network.
      +  -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates.
      +  -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border.
      +  -- The CAP Zone prefix is nil. No CAP is created.
      +  -- The CAP Limit is nil.
      +  -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets.
      +  -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task.
      +  -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement.
      +  -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created.
      +
      +  A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 )  
      +  
    • +
    + +
    +
    +
    +
    + + + +AI_A2A_GCICAP.Templates + +
    +
    + +

    Type list

    +

    Type strig

    + diff --git a/docs/Documentation/AI_A2A_GCI.html b/docs/Documentation/AI_A2A_GCI.html index c6d4b3b60..6541c5258 100644 --- a/docs/Documentation/AI_A2A_GCI.html +++ b/docs/Documentation/AI_A2A_GCI.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -204,79 +215,79 @@ - AI_A2A_GCI.InterceptRoute(AIControllable) + AI_A2A_GCI.InterceptRoute(AIControllable, AIIntercept, Fsm) - AI_A2A_GCI:New(AIGroup, EngageMinSpeed, EngageMaxSpeed) + AI_A2A_GCI:New(AIIntercept, EngageMinSpeed, EngageMaxSpeed)

    Creates a new AIA2AGCI object

    - AI_A2A_GCI:OnAfterAbort(AIGroup, From, Event, To) + AI_A2A_GCI:OnAfterAbort(AIIntercept, From, Event, To)

    OnAfter Transition Handler for Event Abort.

    - AI_A2A_GCI:OnAfterAccomplish(AIGroup, From, Event, To) + AI_A2A_GCI:OnAfterAccomplish(AIIntercept, From, Event, To)

    OnAfter Transition Handler for Event Accomplish.

    - AI_A2A_GCI:OnAfterDestroy(AIGroup, From, Event, To) + AI_A2A_GCI:OnAfterDestroy(AIIntercept, From, Event, To)

    OnAfter Transition Handler for Event Destroy.

    - AI_A2A_GCI:OnAfterEngage(AIGroup, From, Event, To) + AI_A2A_GCI:OnAfterEngage(AIIntercept, From, Event, To)

    OnAfter Transition Handler for Event Engage.

    - AI_A2A_GCI:OnAfterFired(AIGroup, From, Event, To) + AI_A2A_GCI:OnAfterFired(AIIntercept, From, Event, To)

    OnAfter Transition Handler for Event Fired.

    - AI_A2A_GCI:OnBeforeAbort(AIGroup, From, Event, To) + AI_A2A_GCI:OnBeforeAbort(AIIntercept, From, Event, To)

    OnBefore Transition Handler for Event Abort.

    - AI_A2A_GCI:OnBeforeAccomplish(AIGroup, From, Event, To) + AI_A2A_GCI:OnBeforeAccomplish(AIIntercept, From, Event, To)

    OnBefore Transition Handler for Event Accomplish.

    - AI_A2A_GCI:OnBeforeDestroy(AIGroup, From, Event, To) + AI_A2A_GCI:OnBeforeDestroy(AIIntercept, From, Event, To)

    OnBefore Transition Handler for Event Destroy.

    - AI_A2A_GCI:OnBeforeEngage(AIGroup, From, Event, To) + AI_A2A_GCI:OnBeforeEngage(AIIntercept, From, Event, To)

    OnBefore Transition Handler for Event Engage.

    - AI_A2A_GCI:OnBeforeFired(AIGroup, From, Event, To) + AI_A2A_GCI:OnBeforeFired(AIIntercept, From, Event, To)

    OnBefore Transition Handler for Event Fired.

    - AI_A2A_GCI:OnEnterEngaging(AIGroup, From, Event, To) + AI_A2A_GCI:OnEnterEngaging(AIIntercept, From, Event, To)

    OnEnter Transition Handler for State Engaging.

    @@ -288,7 +299,7 @@ - AI_A2A_GCI:OnLeaveEngaging(AIGroup, From, Event, To) + AI_A2A_GCI:OnLeaveEngaging(AIIntercept, From, Event, To)

    OnLeave Transition Handler for State Engaging.

    @@ -342,31 +353,37 @@ - AI_A2A_GCI:onafterAbort(AIGroup, From, Event, To) + AI_A2A_GCI:onafterAbort(AIIntercept, From, Event, To) - AI_A2A_GCI:onafterAccomplish(AIGroup, From, Event, To) + AI_A2A_GCI:onafterAccomplish(AIIntercept, From, Event, To) - AI_A2A_GCI:onafterDestroy(AIGroup, From, Event, To, EventData) + AI_A2A_GCI:onafterDestroy(AIIntercept, From, Event, To, EventData) - AI_A2A_GCI:onafterEngage(AIGroup, From, Event, To) + AI_A2A_GCI:onafterEngage(AIIntercept, From, Event, To)

    onafter State Transition for Event Patrol.

    - AI_A2A_GCI:onbeforeEngage(AIGroup, From, Event, To) + AI_A2A_GCI:onafterStart(AIIntercept, From, Event, To) + +

    onafter State Transition for Event Patrol.

    + + + + AI_A2A_GCI:onbeforeEngage(AIIntercept, From, Event, To) @@ -617,19 +634,29 @@ Use the method AICap#AI -AI_A2A_GCI.InterceptRoute(AIControllable) +AI_A2A_GCI.InterceptRoute(AIControllable, AIIntercept, Fsm)
    -

    Parameter

    +

    Parameters

    @@ -638,7 +665,7 @@ Use the method AICap#AI -AI_A2A_GCI:New(AIGroup, EngageMinSpeed, EngageMaxSpeed) +AI_A2A_GCI:New(AIIntercept, EngageMinSpeed, EngageMaxSpeed)
    @@ -649,7 +676,7 @@ Use the method AICap#AI
  • -

    Wrapper.Group#GROUP AIGroup :

    +

    Wrapper.Group#GROUP AIIntercept :

  • @@ -674,7 +701,7 @@ Use the method AICap#AI -AI_A2A_GCI:OnAfterAbort(AIGroup, From, Event, To) +AI_A2A_GCI:OnAfterAbort(AIIntercept, From, Event, To)
    @@ -685,8 +712,8 @@ Use the method AICap#AI
  • -

    Wrapper.Group#GROUP AIGroup : -The AIGroup Object managed by the FSM.

    +

    Wrapper.Group#GROUP AIIntercept : +The Group Object managed by the FSM.

  • @@ -714,7 +741,7 @@ The To State string.

    -AI_A2A_GCI:OnAfterAccomplish(AIGroup, From, Event, To) +AI_A2A_GCI:OnAfterAccomplish(AIIntercept, From, Event, To)
    @@ -725,8 +752,8 @@ The To State string.

  • +
    +
    + + +AI_A2A_GCI:onafterStart(AIIntercept, From, Event, To) + +
    +
    + +

    onafter State Transition for Event Patrol.

    + +

    Parameters

    +
    -
    -
    - - -_NewEngageCapRoute(AIControllable) - -
    -
    - - - -

    Parameter

    -

    Type AI_Cap

    @@ -630,6 +620,38 @@ Use the method AICap#AI +
    +
    +
    + + +AI_CAP_ZONE.EngageRoute(AI, EngageGroup, Fsm) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      AI : +AICAP#AICAP_ZONE

      + +
    • +
    • + +

      Wrapper.Group#GROUP EngageGroup :

      + +
    • +
    • + +

      Fsm :

      + +
    • +
    diff --git a/docs/Documentation/AI_Cas.html b/docs/Documentation/AI_Cas.html index 068b80a80..9d3885963 100644 --- a/docs/Documentation/AI_Cas.html +++ b/docs/Documentation/AI_Cas.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -161,12 +172,6 @@

    AICASZONE class, extends AIPatrol#AIPATROL_ZONE

    AICASZONE derives from the AIPatrol#AIPATROL_ZONE, inheriting its methods and behaviour.

    - - - - _NewEngageRoute(AIControllable) - - @@ -224,6 +229,12 @@ AI_CAS_ZONE.EngageDirection + + + + AI_CAS_ZONE.EngageRoute(AI, EngageGroup, Fsm) + + @@ -530,27 +541,6 @@ It can be notified to go RTB through the RTB event.


    - -
    -
    -
    - - -_NewEngageRoute(AIControllable) - -
    -
    - - - -

    Parameter

    -

    Type AI_Cas

    @@ -714,6 +704,38 @@ Use the structure DCSTypes#AI.Ta + + +
    +
    + + +AI_CAS_ZONE.EngageRoute(AI, EngageGroup, Fsm) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      AI : +AICAS#AICAS_ZONE

      + +
    • +
    • + +

      Wrapper.Group#GROUP EngageGroup :

      + +
    • +
    • + +

      Fsm :

      + +
    • +
    diff --git a/docs/Documentation/AI_Formation.html b/docs/Documentation/AI_Formation.html index 76e0fbc33..790f66235 100644 --- a/docs/Documentation/AI_Formation.html +++ b/docs/Documentation/AI_Formation.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/AI_Patrol.html b/docs/Documentation/AI_Patrol.html index 43392b308..9e1850d93 100644 --- a/docs/Documentation/AI_Patrol.html +++ b/docs/Documentation/AI_Patrol.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -236,13 +247,13 @@ - AI_PATROL_ZONE:ManageDamage(PatrolDamageTreshold) + AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold)

    When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.

    - AI_PATROL_ZONE:ManageFuel(PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime) + AI_PATROL_ZONE:ManageFuel(PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime)

    When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.

    @@ -404,7 +415,7 @@ - AI_PATROL_ZONE.PatrolDamageTreshold + AI_PATROL_ZONE.PatrolDamageThreshold @@ -416,7 +427,7 @@ - AI_PATROL_ZONE.PatrolFuelTresholdPercentage + AI_PATROL_ZONE.PatrolFuelThresholdPercentage @@ -485,12 +496,6 @@ AI_PATROL_ZONE:SetDetectionDeactivated()

    Deactivate the detection.

    - - - - AI_PATROL_ZONE:SetDetectionInterval(Seconds) - -

    Set the interval in seconds between each detection executed by the AI.

    @@ -509,6 +514,12 @@ AI_PATROL_ZONE:SetDetectionZone(DetectionZone)

    Set the detection zone where the AI is detecting targets.

    + + + + AI_PATROL_ZONE:SetRefreshTimeInterval(Seconds) + +

    Set the interval in seconds between each detection executed by the AI.

    @@ -742,7 +753,7 @@ Only put detection on when absolutely necessary, and the frequency of the detect
  • AIPATROLZONE.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 AIPATROLZONE.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 AIPATROLZONE.SetRefreshTimeInterval( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. Use the method AIPATROLZONE.GetDetectedUnits() to obtain a list of the Units detected by the AI.

    The detection can be filtered to potential targets in a specific zone. @@ -821,7 +832,7 @@ Use the method AIPATROLZONE.M

    - Functional.Spawn#SPAWN + Core.Spawn#SPAWN AI_PATROL_ZONE.CoordTest @@ -926,9 +937,6 @@ Use the method AIPATROLZONE.M - -

    This table contains the targets detected during patrol.

    -
    @@ -957,7 +965,7 @@ The list of Unit#UNITs

    -AI_PATROL_ZONE:ManageDamage(PatrolDamageTreshold) +AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold)
    @@ -975,7 +983,7 @@ So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold
    -
    -
    - - - -ACT_ACCOUNT_DEADS.TaskName - -
    -
    - - -
    -ACT_ACCOUNT_DEADS:onafterEvent(ProcessClient, Task, From, Event, To, EventData) +ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit, Task, From, Event, To, EventData)
    @@ -777,7 +756,7 @@ Each successful dead will trigger an Account state transition that can be scored
    -
    -
    - - -ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData) - -
    -
    - - - -

    Parameter

    - -
    -
    -
    -
    - - -ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) - -
    -
    - - - -

    Parameter

    - -
    diff --git a/docs/Documentation/Airbase.html b/docs/Documentation/Airbase.html index 47a104aa6..fa6abc332 100644 --- a/docs/Documentation/Airbase.html +++ b/docs/Documentation/Airbase.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -138,7 +149,7 @@ AIRBASE.Caucasus - +

    Enumeration to identify the airbases in the Caucasus region.

    @@ -157,6 +168,12 @@ AIRBASE:GetDCSObject() + + + + AIRBASE:GetZone() + +

    Get the airbase zone.

    @@ -248,7 +265,36 @@ is implemented in the AIRBASE class as AIRBASE.Get
    +

    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 +
    • +
    @@ -317,6 +363,24 @@ self

    + + +
    +
    + + +AIRBASE:GetZone() + +
    +
    + +

    Get the airbase zone.

    + +

    Return value

    + +

    Core.Zone#ZONE_RADIUS: +The zone radius of the airbase.

    +
    diff --git a/docs/Documentation/AirbasePolice.html b/docs/Documentation/AirbasePolice.html index 218a7d247..7e4b6b2aa 100644 --- a/docs/Documentation/AirbasePolice.html +++ b/docs/Documentation/AirbasePolice.html @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,14 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -105,79 +115,59 @@

    Module AirbasePolice

    -

    Functional -- This module monitors airbases traffic.

    +

    Functional -- The AIRBASEPOLICE classes monitor airbase traffic and regulate speed while taxiing.


    -

    1) AirbasePolice#AIRBASEPOLICE_BASE class, extends Base#BASE

    -

    The AirbasePolice#AIRBASEPOLICE_BASE class provides the main methods to monitor CLIENT behaviour at airbases. -CLIENTS should not be allowed to:

    - -
      -
    • Don't taxi faster than 40 km/h.
    • -
    • Don't take-off on taxiways.
    • -
    • Avoid to hit other planes on the airbase.
    • -
    • Obey ground control orders.
    • -
    - -

    2) AirbasePolice#AIRBASEPOLICE_CAUCASUS class, extends AirbasePolice#AIRBASEPOLICE_BASE

    -

    All the airbases on the caucasus map can be monitored using this class. -If you want to monitor specific airbases, you need to use the AIRBASEPOLICE_BASE.Monitor() method, which takes a table or airbase names. -The following names can be given: - * AnapaVityazevo - * Batumi - * Beslan - * Gelendzhik - * Gudauta - * Kobuleti - * KrasnodarCenter - * KrasnodarPashkovsky - * Krymsk - * Kutaisi - * MaykopKhanskaya - * MineralnyeVody - * Mozdok - * Nalchik - * Novorossiysk - * SenakiKolkhi - * SochiAdler - * Soganlug - * SukhumiBabushara - * TbilisiLochini - * Vaziani

    - -

    3) AirbasePolice#AIRBASEPOLICE_NEVADA class, extends AirbasePolice#AIRBASEPOLICE_BASE

    -

    All the airbases on the NEVADA map can be monitored using this class. -If you want to monitor specific airbases, you need to use the AIRBASEPOLICE_BASE.Monitor() method, which takes a table or airbase names. -The following names can be given: - * Nellis - * McCarran - * Creech - * Groom Lake

    -

    Contributions: Dutch Baron - Concept & Testing

    Author: FlightControl - Framework Design & Programming

    +
    +

    Global(s)

    + + + + + + + + @@ -185,15 +175,63 @@ The following names can be given:

    Type AIRBASEPOLICE_BASE

    AIRBASEPOLICE_BASE - +

    Base class for AIRBASEPOLICE implementations.

    AIRBASEPOLICE_CAUCASUS +

    AIRBASEPOLICE_CAUCASUS, extends #AIRBASEPOLICE_BASE

    +

    Banner Image

    + +

    The AIRBASEPOLICE_CAUCASUS class monitors the speed of the airplanes at the airbase during taxi.

    AIRBASEPOLICE_NEVADA +

    AIRBASEPOLICE_NEVADA, extends #AIRBASEPOLICE_BASE

    + +

    Banner Image

    + +

    The AIRBASEPOLICE_NEVADA class monitors the speed of the airplanes at the airbase during taxi.

    +
    AIRBASEPOLICE_NORMANDY +

    AIRBASEPOLICE_NORMANDY, extends #AIRBASEPOLICE_BASE

    + +

    Banner Image

    + +

    The AIRBASEPOLICE_NORMANDY class monitors the speed of the airplanes at the airbase during taxi.

    +
    SSB
    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AIRBASEPOLICE_BASE.AirbaseNamesAIRBASEPOLICE_BASE.AirbaseList +
    AIRBASEPOLICE_BASE.AirbaseMonitor + +
    AIRBASEPOLICE_BASE.Airbases + +
    AIRBASEPOLICE_BASE.KickSpeed + +
    AIRBASEPOLICE_BASE:New(Airbases, AirbaseList) +

    Creates a new AIRBASEPOLICE_BASE object.

    AIRBASEPOLICE_BASE.SetClient +
    AIRBASEPOLICE_BASE:SetKickSpeedKmph(KickSpeed) +

    Set the maximum speed in Kmph until the player gets kicked.

    +
    AIRBASEPOLICE_BASE:SetKickSpeedMiph(KickSpeedMiph) +

    Set the maximum speed in Miph until the player gets kicked.

    +
    AIRBASEPOLICE_BASE:SmokeRunways(SmokeColor) +

    Smoke the airbases runways.

    +
    AIRBASEPOLICE_BASE:_AirbaseMonitor() +
    @@ -201,9 +239,9 @@ The following names can be given:

    Type AIRBASEPOLICE_CAUCASUS

    - +
    AIRBASEPOLICE_CAUCASUS.SetClientAIRBASEPOLICE_CAUCASUS:New(AirbaseNames) - +

    Creates a new AIRBASEPOLICE_CAUCASUS object.

    @@ -211,21 +249,19 @@ The following names can be given:

    Type AIRBASEPOLICE_NEVADA

    - - - - - - - - - + + +
    AIRBASEPOLICE_NEVADA.Airbases - -
    AIRBASEPOLICE_NEVADA.ClassName - -
    AIRBASEPOLICE_NEVADA:New(SetClient)AIRBASEPOLICE_NEVADA:New(AirbaseNames)

    Creates a new AIRBASEPOLICE_NEVADA object.

    +
    + +

    Type AIRBASEPOLICE_NORMANDY

    + + + +
    AIRBASEPOLICE_NORMANDY:New(AirbaseNames) +

    Creates a new AIRBASEPOLICE_NORMANDY object.

    @@ -234,27 +270,101 @@ The following names can be given:
    - + #AIRBASEPOLICE_BASE AIRBASEPOLICE_BASE
    - +

    Base class for AIRBASEPOLICE implementations.

    - + #AIRBASEPOLICE_CAUCASUS AIRBASEPOLICE_CAUCASUS
    +

    AIRBASEPOLICE_CAUCASUS, extends #AIRBASEPOLICE_BASE

    + +

    Banner Image

    + +

    The AIRBASEPOLICE_CAUCASUS class monitors the speed of the airplanes at the airbase during taxi.

    + + +

    The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.

    + +

    The maximum speed for the airbases at Caucasus is 50 km/h.

    + +

    The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +faster than the maximum allowed speed, the pilot will be kicked.

    + +

    Different airbases have different maximum speeds, according safety regulations.

    + +

    Airbases monitored

    + +

    The following airbases are monitored at the Caucasus region:

    + +
      +
    • Anapa Vityazevo
    • +
    • Batumi
    • +
    • Beslan
    • +
    • Gelendzhik
    • +
    • Gudauta
    • +
    • Kobuleti
    • +
    • Krasnodar Center
    • +
    • Krasnodar Pashkovsky
    • +
    • Krymsk
    • +
    • Kutaisi
    • +
    • Maykop Khanskaya
    • +
    • Mineralnye Vody
    • +
    • Mozdok
    • +
    • Nalchik
    • +
    • Novorossiysk
    • +
    • Senaki Kolkhi
    • +
    • Sochi Adler
    • +
    • Soganlug
    • +
    • Sukhumi Babushara
    • +
    • Tbilisi Lochini
    • +
    • Vaziani
    • +
    + + +

    Installation

    + +

    In Single Player Missions

    + +

    AIRBASEPOLICE is fully functional in single player.

    + +

    In Multi Player Missions

    + +

    AIRBASEPOLICE is NOT functional in multi player, for client machines connecting to the server, running the mission. +Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +To work around this problem, a much better solution has been made, using the slot blocker script designed +by Ciribob. With the help of Ciribob, this script has been extended to also kick client players while in flight. +AIRBASEPOLICE is communicating with this modified script to kick players!

    + +

    Install the file SimpleSlotBlockGameGUI.lua on the server, following the installation instructions described by Ciribob.

    + +

    Simple Slot Blocker from Ciribob & FlightControl

    + +

    Script it!

    + +

    1. AIRBASEPOLICE_CAUCASUS Constructor

    + +

    Creates a new AIRBASEPOLICE_CAUCASUS object that will monitor pilots taxiing behaviour.

    + +
    -- This creates a new AIRBASEPOLICE_CAUCASUS object.
    +
    +-- Monitor for these clients the airbases.
    +AirbasePoliceCaucasus = AIRBASEPOLICE_CAUCASUS:New()
    +
    @@ -269,7 +379,148 @@ The following names can be given:
    +

    AIRBASEPOLICE_NEVADA, extends #AIRBASEPOLICE_BASE

    +

    Banner Image

    + +

    The AIRBASEPOLICE_NEVADA class monitors the speed of the airplanes at the airbase during taxi.

    + + +

    The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.

    + +

    The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +faster than the maximum allowed speed, the pilot will be kicked.

    + +

    Different airbases have different maximum speeds, according safety regulations.

    + +

    Airbases monitored

    + +

    The following airbases are monitored at the Caucasus region:

    + +
      +
    • Nellis
    • +
    • McCarran
    • +
    • Creech
    • +
    • GroomLake
    • +
    + + +

    Installation

    + +

    In Single Player Missions

    + +

    AIRBASEPOLICE is fully functional in single player.

    + +

    In Multi Player Missions

    + +

    AIRBASEPOLICE is NOT functional in multi player, for client machines connecting to the server, running the mission. +Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +To work around this problem, a much better solution has been made, using the slot blocker script designed +by Ciribob. With the help of Ciribob, this script has been extended to also kick client players while in flight. +AIRBASEPOLICE is communicating with this modified script to kick players!

    + +

    Install the file SimpleSlotBlockGameGUI.lua on the server, following the installation instructions described by Ciribob.

    + +

    Simple Slot Blocker from Ciribob & FlightControl

    + +

    Script it!

    + +

    1. AIRBASEPOLICE_NEVADA Constructor

    + +

    Creates a new AIRBASEPOLICE_NEVADA object that will monitor pilots taxiing behaviour.

    + +
    -- This creates a new AIRBASEPOLICE_NEVADA object.
    +
    +-- Monitor for these clients the airbases.
    +AirbasePoliceCaucasus = AIRBASEPOLICE_NEVADA:New()
    +
    + + +
    +
    +
    +
    + + #AIRBASEPOLICE_NORMANDY + +AIRBASEPOLICE_NORMANDY + +
    +
    + +

    AIRBASEPOLICE_NORMANDY, extends #AIRBASEPOLICE_BASE

    + +

    Banner Image

    + +

    The AIRBASEPOLICE_NORMANDY class monitors the speed of the airplanes at the airbase during taxi.

    + + +

    The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.

    + +

    The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +faster than the maximum allowed speed, the pilot will be kicked.

    + +

    Different airbases have different maximum speeds, according safety regulations.

    + +

    Airbases monitored

    + +

    The following airbases are monitored at the Caucasus region:

    + +
      +
    • Nellis
    • +
    • McCarran
    • +
    • Creech
    • +
    • GroomLake
    • +
    + + +

    Installation

    + +

    In Single Player Missions

    + +

    AIRBASEPOLICE is fully functional in single player.

    + +

    In Multi Player Missions

    + +

    AIRBASEPOLICE is NOT functional in multi player, for client machines connecting to the server, running the mission. +Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +To work around this problem, a much better solution has been made, using the slot blocker script designed +by Ciribob. With the help of Ciribob, this script has been extended to also kick client players while in flight. +AIRBASEPOLICE is communicating with this modified script to kick players!

    + +

    Install the file SimpleSlotBlockGameGUI.lua on the server, following the installation instructions described by Ciribob.

    + +

    Simple Slot Blocker from Ciribob & FlightControl

    + +

    Script it!

    + +

    1. AIRBASEPOLICE_NORMANDY Constructor

    + +

    Creates a new AIRBASEPOLICE_NORMANDY object that will monitor pilots taxiing behaviour.

    + +
    -- This creates a new AIRBASEPOLICE_NORMANDY object.
    +
    +-- Monitor for these clients the airbases.
    +AirbasePoliceCaucasus = AIRBASEPOLICE_NORMANDY:New()
    +
    + + +
    +
    +
    +
    + + + +SSB + +
    +
    + + + + +

    This is simple slot blocker is used on the server.

    @@ -281,14 +532,88 @@ The following names can be given:
    - -AIRBASEPOLICE_BASE.AirbaseNames + +AIRBASEPOLICE_BASE.AirbaseList
    +
    +
    +
    +
    + + + +AIRBASEPOLICE_BASE.AirbaseMonitor + +
    +
    + + + +
    +
    +
    +
    + + + +AIRBASEPOLICE_BASE.Airbases + +
    +
    + + + +
    +
    +
    +
    + + + +AIRBASEPOLICE_BASE.KickSpeed + +
    +
    + + + +
    +
    +
    +
    + + +AIRBASEPOLICE_BASE:New(Airbases, AirbaseList) + +
    +
    + +

    Creates a new AIRBASEPOLICE_BASE object.

    + +

    Parameters

    +
      +
    • + +

      Airbases : +A table of Airbase Names.

      + +
    • +
    • + +

      AirbaseList :

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_BASE: +self

    +
    @@ -303,6 +628,100 @@ The following names can be given: + +
    +
    +
    + + +AIRBASEPOLICE_BASE:SetKickSpeedKmph(KickSpeed) + +
    +
    + +

    Set the maximum speed in Kmph until the player gets kicked.

    + +

    Parameter

    +
      +
    • + +

      #number KickSpeed : +Set the maximum speed in Kmph until the player gets kicked.

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_BASE: +self

    + +
    +
    +
    +
    + + +AIRBASEPOLICE_BASE:SetKickSpeedMiph(KickSpeedMiph) + +
    +
    + +

    Set the maximum speed in Miph until the player gets kicked.

    + +

    Parameter

    +
      +
    • + +

      #number KickSpeedMiph : +Set the maximum speed in Mph until the player gets kicked.

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_BASE: +self

    + +
    +
    +
    +
    + + +AIRBASEPOLICE_BASE:SmokeRunways(SmokeColor) + +
    +
    + +

    Smoke the airbases runways.

    + +

    Parameter

    + +

    Return value

    + +

    #AIRBASEPOLICE_BASE: +self

    + +
    +
    +
    +
    + + +AIRBASEPOLICE_BASE:_AirbaseMonitor() + +
    +
    + + +
    @@ -313,14 +732,27 @@ The following names can be given:
    - Core.Set#SET_CLIENT - -AIRBASEPOLICE_CAUCASUS.SetClient + +AIRBASEPOLICE_CAUCASUS:New(AirbaseNames)
    +

    Creates a new AIRBASEPOLICE_CAUCASUS object.

    +

    Parameter

    +
      +
    • + +

      AirbaseNames : +A list {} of airbase names (Use AIRBASE.Caucasus enumerator).

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_CAUCASUS: +self

    @@ -330,36 +762,8 @@ The following names can be given:
    - - -AIRBASEPOLICE_NEVADA.Airbases - -
    -
    - - - -
    -
    -
    -
    - - #string - -AIRBASEPOLICE_NEVADA.ClassName - -
    -
    - - - -
    -
    -
    -
    - -AIRBASEPOLICE_NEVADA:New(SetClient) +AIRBASEPOLICE_NEVADA:New(AirbaseNames)
    @@ -370,8 +774,8 @@ The following names can be given:
    • -

      SetClient : -A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase.

      +

      AirbaseNames : +A list {} of airbase names (Use AIRBASE.Nevada enumerator).

    @@ -380,6 +784,36 @@ A SET_CLIENT object that will contain the CLIENT objects to be monitored if they

    #AIRBASEPOLICE_NEVADA: self

    +
    +
    + +

    Type AIRBASEPOLICE_NORMANDY

    +

    Field(s)

    +
    +
    + + +AIRBASEPOLICE_NORMANDY:New(AirbaseNames) + +
    +
    + +

    Creates a new AIRBASEPOLICE_NORMANDY object.

    + +

    Parameter

    +
      +
    • + +

      AirbaseNames : +A list {} of airbase names (Use AIRBASE.Normandy enumerator).

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_NORMANDY: +self

    +
    diff --git a/docs/Documentation/Assign.html b/docs/Documentation/Assign.html index d96b1fe01..c49fd73d2 100644 --- a/docs/Documentation/Assign.html +++ b/docs/Documentation/Assign.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -956,7 +967,7 @@ self

    -

    Parameter

    +

    Parameters

    • #BASE Child : is the Child class from which the Parent class needs to be retrieved.

      +
    • +
    • + +

      FromClass :

      +

    Return value

    @@ -1230,6 +1306,46 @@ is the Parent class that the Child inherits from.

    #BASE: Child

    + + +
    +
    + + +BASE:IsInstanceOf(ClassName) + +
    +
    + +

    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

    • +
    + + +

    Parameter

    +
      +
    • + +

      ClassName : +is the name of the class or the class itself to run the check against

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + +
    @@ -1829,6 +1945,144 @@ The EventData structure.

    + +
    +
    +
    + + +BASE:ScheduleOnce(Start, SchedulerFunction, ...) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #number Start : +Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.

      + +
    • +
    • + +

      #function SchedulerFunction : +The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.

      + +
    • +
    • + +

      #table ... : +Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.

      + +
    • +
    +

    Return value

    + +

    #number: +The ScheduleID of the planned schedule.

    + +
    +
    +
    +
    + + +BASE:ScheduleRepeat(Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ...) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #number Start : +Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.

      + +
    • +
    • + +

      #number Repeat : +Specifies the interval in seconds when the scheduler will call the event function.

      + +
    • +
    • + +

      #number RandomizeFactor : +Specifies a randomization factor between 0 and 1 to randomize the Repeat.

      + +
    • +
    • + +

      #number Stop : +Specifies the amount of seconds when the scheduler will be stopped.

      + +
    • +
    • + +

      #function SchedulerFunction : +The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.

      + +
    • +
    • + +

      #table ... : +Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.

      + +
    • +
    +

    Return value

    + +

    #number: +The ScheduleID of the planned schedule.

    + +
    +
    +
    +
    + + +BASE:ScheduleStop(SchedulerFunction) + +
    +
    + +

    Stops the Schedule.

    + +

    Parameter

    +
      +
    • + +

      #function SchedulerFunction : +The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.

      + +
    • +
    +
    +
    +
    +
    + + + +BASE.SchedulerObject + +
    +
    + + +
    @@ -2143,8 +2397,9 @@ BASE:TraceOnOff( false )
    - -BASE:_Destructor() + #BASE._ + +BASE._
    @@ -2191,22 +2446,6 @@ A #table or any field.

    - -BASE:_SetDestructor() - -
    -
    - - - - -

    THIS IS WHY WE NEED LUA 5.2 ...

    - -
    -
    -
    -
    - BASE:_T(Arguments, DebugInfoCurrentParam, DebugInfoFromParam) @@ -2234,6 +2473,20 @@ A #table or any field.

    +
    +
    +
    +
    + + #BASE.__ + +BASE.__ + +
    +
    + + +
    @@ -2264,6 +2517,10 @@ A #table or any field.

    +

    Type BASE._

    + +

    Type BASE.__

    +

    Type FORMATION

    The Formation Class

    diff --git a/docs/Documentation/Cargo.html b/docs/Documentation/Cargo.html index d774a472f..24cc7973c 100644 --- a/docs/Documentation/Cargo.html +++ b/docs/Documentation/Cargo.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -160,6 +171,14 @@ CARGOS + + + + CARGO_CRATE + +

    CARGO_CRATE class, extends #CARGO_REPRESENTABLE

    + +

    The CARGO_CRATE class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.

    @@ -185,7 +204,7 @@ CARGO_REPRESENTABLE - +

    Models CARGO that is representable by a Unit.

    @@ -227,6 +246,48 @@ CARGO.Containable

    This flag defines if the cargo can be contained within a DCS Unit.

    + + + + CARGO.Deployed + + + + + + CARGO:Destroy() + +

    Destroy the cargo.

    + + + + CARGO:Flare(FlareColor) + +

    Signal a flare at the position of the CARGO.

    + + + + CARGO:FlareGreen() + +

    Signal a green flare at the position of the CARGO.

    + + + + CARGO:FlareRed() + +

    Signal a red flare at the position of the CARGO.

    + + + + CARGO:FlareWhite() + +

    Signal a white flare at the position of the CARGO.

    + + + + CARGO:FlareYellow() + +

    Signal a yellow flare at the position of the CARGO.

    @@ -263,6 +324,18 @@ CARGO:IsAlive()

    Check if cargo is alive.

    + + + + CARGO:IsDeployed() + +

    Is the cargo deployed

    + + + + CARGO:IsDestroyed() + +

    Check if cargo is destroyed.

    @@ -371,6 +444,12 @@ CARGO.Representable

    This flag defines if the cargo can be represented by a DCS Unit.

    + + + + CARGO:SetDeployed(Deployed) + +

    Set the cargo as deployed

    @@ -383,6 +462,42 @@ CARGO.Slingloadable

    This flag defines if the cargo can be slingloaded.

    + + + + CARGO:Smoke(SmokeColor, Range) + +

    Smoke the CARGO.

    + + + + CARGO:SmokeBlue() + +

    Smoke the CARGO Blue.

    + + + + CARGO:SmokeGreen() + +

    Smoke the CARGO Green.

    + + + + CARGO:SmokeOrange() + +

    Smoke the CARGO Orange.

    + + + + CARGO:SmokeRed() + +

    Smoke the CARGO Red.

    + + + + CARGO:SmokeWhite() + +

    Smoke the CARGO White.

    @@ -437,6 +552,46 @@ CARGO:__UnLoad(DelaySeconds, ToPointVec2)

    UnLoads the cargo to a Carrier.

    + + + + +

    Type CARGO_CRATE

    + + + + + + + + + + + + + + + + + + + + + + + +
    CARGO_CRATE.CargoCarrier + +
    CARGO_CRATE.CargoObject + +
    CARGO_CRATE:New(CrateName, Type, Name, Weight, ReportRadius, NearRadius, CargoCrateName) +

    CARGO_CRATE Constructor.

    +
    CARGO_CRATE.OnUnLoadedCallBack + +
    CARGO_CRATE:onenterLoaded(Event, From, To, CargoCarrier) +

    Loaded State.

    +
    CARGO_CRATE:onenterUnLoaded(Event, From, To, Core, ToPointVec2) +

    Enter UnLoaded State.

    @@ -447,6 +602,12 @@ CARGO_GROUP.CargoCarrier + + + + CARGO_GROUP.CargoGroup + + @@ -456,15 +617,27 @@ - CARGO_GROUP.CargoSet + CARGO_GROUP:GetCount() - +

    Get the amount of cargo units in the group.

    CARGO_GROUP:New(CargoGroup, Type, Name, ReportRadius, NearRadius)

    CARGO_GROUP constructor.

    + + + + CARGO_GROUP:OnEventCargoDead(EventData) + + + + + + CARGO_GROUP:RespawnOnDestroyed(RespawnDestroyed) + +

    Respawn the cargo when destroyed

    @@ -483,6 +656,12 @@ CARGO_GROUP:onenterBoarding(CargoCarrier, Event, From, To, NearRadius, ...)

    Enter Boarding State.

    + + + + CARGO_GROUP:onenterDestroyed() + + @@ -587,6 +766,12 @@ CARGO_REPORTABLE.CargoObject + + + + CARGO_REPORTABLE.CargoSet + + @@ -623,6 +808,12 @@ CARGO_REPORTABLE.ReportRadius + + + + CARGO_REPORTABLE:Respawn() + +

    Respawn the cargo.

    @@ -630,6 +821,12 @@

    Type CARGO_REPRESENTABLE

    + + + + - - - - @@ -816,6 +1007,28 @@ There are 2 moments when state transition methods will be called by the state ma + + +
    +
    + + #CARGO_CRATE + +CARGO_CRATE + +
    +
    + +

    CARGO_CRATE class, extends #CARGO_REPRESENTABLE

    + +

    The CARGO_CRATE class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.

    + + +

    Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO_CRATE objects to and from carriers.

    + +
    + +
    @@ -876,7 +1089,7 @@ There are 2 moments when state transition methods will be called by the state ma
    - +

    Models CARGO that is representable by a Unit.

    @@ -941,7 +1154,7 @@ The radius when the cargo will board the Carrier (to avoid collision).

    - Wrapper.Controllable#CONTROLLABLE + Wrapper.Client#CLIENT CARGO.CargoCarrier @@ -992,6 +1205,106 @@ The radius when the cargo will board the Carrier (to avoid collision).

    This flag defines if the cargo can be contained within a DCS Unit.

    + +
    +
    +
    + + + +CARGO.Deployed + +
    +
    + + + +
    +
    +
    +
    + + +CARGO:Destroy() + +
    +
    + +

    Destroy the cargo.

    + +
    +
    +
    +
    + + +CARGO:Flare(FlareColor) + +
    +
    + +

    Signal a flare at the position of the CARGO.

    + +

    Parameter

    + +
    +
    +
    +
    + + +CARGO:FlareGreen() + +
    +
    + +

    Signal a green flare at the position of the CARGO.

    + +
    +
    +
    +
    + + +CARGO:FlareRed() + +
    +
    + +

    Signal a red flare at the position of the CARGO.

    + +
    +
    +
    +
    + + +CARGO:FlareWhite() + +
    +
    + +

    Signal a white flare at the position of the CARGO.

    + +
    +
    +
    +
    + + +CARGO:FlareYellow() + +
    +
    + +

    Signal a yellow flare at the position of the CARGO.

    +
    @@ -1105,6 +1418,42 @@ true if unloaded

    + +CARGO:IsDeployed() + +
    +
    + +

    Is the cargo deployed

    + +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +CARGO:IsDestroyed() + +
    +
    + +

    Check if cargo is destroyed.

    + +

    Return value

    + +

    #boolean: +true if destroyed

    + +
    +
    +
    +
    + CARGO:IsInZone(Zone) @@ -1520,6 +1869,27 @@ The radius when the cargo will board the Carrier (to avoid collision).

    + +CARGO:SetDeployed(Deployed) + +
    +
    + +

    Set the cargo as deployed

    + +

    Parameter

    +
      +
    • + +

      Deployed :

      + +
    • +
    +
    +
    +
    +
    + CARGO:SetWeight(Weight) @@ -1561,6 +1931,97 @@ The weight in kg.

    + +CARGO:Smoke(SmokeColor, Range) + +
    +
    + +

    Smoke the CARGO.

    + +

    Parameters

    +
      +
    • + +

      SmokeColor :

      + +
    • +
    • + +

      Range :

      + +
    • +
    +
    +
    +
    +
    + + +CARGO:SmokeBlue() + +
    +
    + +

    Smoke the CARGO Blue.

    + +
    +
    +
    +
    + + +CARGO:SmokeGreen() + +
    +
    + +

    Smoke the CARGO Green.

    + +
    +
    +
    +
    + + +CARGO:SmokeOrange() + +
    +
    + +

    Smoke the CARGO Orange.

    + +
    +
    +
    +
    + + +CARGO:SmokeRed() + +
    +
    + +

    Smoke the CARGO Red.

    + +
    +
    +
    +
    + + +CARGO:SmokeWhite() + +
    +
    + +

    Smoke the CARGO White.

    + +
    +
    +
    +
    + CARGO:Spawn(PointVec2) @@ -1801,6 +2262,189 @@ The amount of seconds to delay the action.

    Type CARGO.CargoObjects

    +

    Type CARGO_CRATE

    + +

    Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters using the DCS menus.

    + +

    Field(s)

    +
    +
    + + + +CARGO_CRATE.CargoCarrier + +
    +
    + + + +
    +
    +
    +
    + + + +CARGO_CRATE.CargoObject + +
    +
    + + + +
    +
    +
    +
    + + +CARGO_CRATE:New(CrateName, Type, Name, Weight, ReportRadius, NearRadius, CargoCrateName) + +
    +
    + +

    CARGO_CRATE Constructor.

    + +

    Parameters

    +
      +
    • + +

      #string CrateName :

      + +
    • +
    • + +

      #string Type :

      + +
    • +
    • + +

      #string Name :

      + +
    • +
    • + +

      #number Weight :

      + +
    • +
    • + +

      #number ReportRadius : +(optional)

      + +
    • +
    • + +

      #number NearRadius : +(optional)

      + +
    • +
    • + +

      CargoCrateName :

      + +
    • +
    +

    Return value

    + +

    #CARGO_CRATE:

    + + +
    +
    +
    +
    + + +CARGO_CRATE.OnUnLoadedCallBack + +
    +
    + + + +
    +
    +
    +
    + + +CARGO_CRATE:onenterLoaded(Event, From, To, CargoCarrier) + +
    +
    + +

    Loaded State.

    + +

    Parameters

    +
      +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    • + +

      Wrapper.Unit#UNIT CargoCarrier :

      + +
    • +
    +
    +
    +
    +
    + + +CARGO_CRATE:onenterUnLoaded(Event, From, To, Core, ToPointVec2) + +
    +
    + +

    Enter UnLoaded State.

    + +

    Parameters

    +
      +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    • + +

      Core : +Point#POINT_VEC2

      + +
    • +
    • + +

      ToPointVec2 :

      + +
    • +
    +
    +
    +

    Type CARGO_GROUP

    Field(s)

    @@ -1815,6 +2459,23 @@ The amount of seconds to delay the action.

    + +

    self.CargoObject:Destroy()

    + + +
    +
    +
    + + + +CARGO_GROUP.CargoGroup + +
    +
    + + +
    @@ -1834,13 +2495,17 @@ The amount of seconds to delay the action.

    - - -CARGO_GROUP.CargoSet + +CARGO_GROUP:GetCount()
    +

    Get the amount of cargo units in the group.

    + +

    Return value

    + +

    #CARGO_GROUP:

    @@ -1896,6 +2561,48 @@ The amount of seconds to delay the action.

    + +CARGO_GROUP:OnEventCargoDead(EventData) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + +CARGO_GROUP:RespawnOnDestroyed(RespawnDestroyed) + +
    +
    + +

    Respawn the cargo when destroyed

    + +

    Parameter

    +
      +
    • + +

      #boolean RespawnDestroyed :

      + +
    • +
    +
    +
    +
    +
    + CARGO_GROUP:onafterBoarding(CargoCarrier, Event, From, To, NearRadius, ...) @@ -2029,6 +2736,19 @@ The amount of seconds to delay the action.

    + +
    +
    +
    + + +CARGO_GROUP:onenterDestroyed() + +
    +
    + + +
    @@ -2667,6 +3387,20 @@ The UNIT carrying the package.

    + +
    +
    +
    + + Core.Set#SET_CARGO + +CARGO_REPORTABLE.CargoSet + +
    +
    + + +
    @@ -2823,6 +3557,19 @@ The range till cargo will board.

    + +
    +
    +
    + + +CARGO_REPORTABLE:Respawn() + +
    +
    + +

    Respawn the cargo.

    +
    @@ -2831,6 +3578,24 @@ The range till cargo will board.

    + +CARGO_REPRESENTABLE:Destroy() + +
    +
    + +

    CARGO_REPRESENTABLE Destructor.

    + +

    Return value

    + +

    #CARGO_REPRESENTABLE:

    + + +
    +
    +
    +
    + CARGO_REPRESENTABLE:New(Type, Name, Weight, ReportRadius, NearRadius, CargoObject) @@ -2928,7 +3693,7 @@ The range till cargo will board.

    Type CARGO_UNIT

    -

    Hello

    +

    Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded.

    Field(s)

    @@ -2970,24 +3735,6 @@ The range till cargo will board.

    - -
    -
    -
    - - -CARGO_UNIT:Destroy() - -
    -
    - -

    CARGO_UNIT Destructor.

    - -

    Return value

    - -

    #CARGO_UNIT:

    - -
    @@ -3059,7 +3806,6 @@ The range till cargo will board.

    - #number CARGO_UNIT.RunCount @@ -3410,8 +4156,6 @@ Point#POINT_VEC2

    -

    Type COMMANDCENTER

    -

    Type sring

    diff --git a/docs/Documentation/CleanUp.html b/docs/Documentation/CleanUp.html index 3433cec11..6338da226 100644 --- a/docs/Documentation/CleanUp.html +++ b/docs/Documentation/CleanUp.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -105,118 +116,72 @@

    Module CleanUp

    -

    Functional -- The CLEANUP class keeps an area clean of crashing or colliding airplanes.

    +

    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:

    + +
    + +

    Global(s)

    CARGO_REPRESENTABLE:Destroy() +

    CARGO_REPRESENTABLE Destructor.

    +
    CARGO_REPRESENTABLE:New(Type, Name, Weight, ReportRadius, NearRadius, CargoObject)

    CARGO_REPRESENTABLE Constructor.

    @@ -667,12 +864,6 @@
    CARGO_UNIT.CargoObject -
    CARGO_UNIT:Destroy() -

    CARGO_UNIT Destructor.

    - +
    CLEANUPCLEANUP_AIRBASE +

    CLEANUP_AIRBASE, extends Base#BASE

    +

    Banner Image

    + +

    The CLEANUP_AIRBASE class keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.

    -

    Type CLEANUP

    +

    Type CLEANUP_AIRBASE

    - + - - - - - - - - - + - + + + + + + + + + - - -
    CLEANUP.ClassNameCLEANUP_AIRBASE:AddAirbase(AirbaseName) - +

    Adds an airbase to the airbase validation list.

    CLEANUP.CleanUpList - -
    CLEANUP.CleanUpScheduler - -
    CLEANUP:New(ZoneNames, TimeInterval)CLEANUP_AIRBASE:New(<, AirbaseNames)

    Creates the main object which is handling the cleaning of the debris within the given Zone Names.

    CLEANUP.TimeIntervalCLEANUP_AIRBASE:RemoveAirbase(AirbaseName) +

    Removes an airbase from the airbase validation list.

    +
    CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles) +

    Enables or disables the cleaning of missiles within the airbase zones.

    +
    CLEANUP_AIRBASE.__
    CLEANUP.ZoneNames +
    - - +

    Type CLEANUP_AIRBASE.__

    + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CLEANUP:_AddForCleanUp(CleanUpUnit, CleanUpUnitName)CLEANUP_AIRBASE.__.< -

    Add the DCSWrapper.Unit#Unit to the CleanUpList for CleanUp.

    -
    CLEANUP:_CleanUpScheduler() -

    At the defined time interval, CleanUp the Groups within the CleanUpList.

    -
    CLEANUP:_DestroyGroup(GroupObject, CleanUpGroupName) -

    Destroys a group from the simulator, but checks first if it is still existing!

    -
    CLEANUP:_DestroyMissile(MissileObject) - -
    CLEANUP:_DestroyUnit(CleanUpUnit, CleanUpUnitName) -

    Destroys a DCSWrapper.Unit#Unit from the simulator, but checks first if it is still existing!

    -
    CLEANUP:_EventAddForCleanUp(event, Event) -

    Detects if the Unit has an SEVENTENGINESHUTDOWN or an SEVENT_HIT within the given ZoneNames.

    -
    CLEANUP:_EventCrash(event, Event) -

    Detects if a crash event occurs.

    -
    CLEANUP:_EventHitCleanUp(event, Event) -

    Detects if the Unit has an SEVENTHIT within the given ZoneNames.

    -
    CLEANUP:_EventShot(event, Event) -

    Detects if a unit shoots a missile.

    -
    CLEANUP:_OnEventBirth(EventData) - +

    string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases.

    @@ -225,34 +190,100 @@
    - #CLEANUP - -CLEANUP + #CLEANUP_AIRBASE + +CLEANUP_AIRBASE
    +

    CLEANUP_AIRBASE, extends Base#BASE

    + +

    Banner Image

    + +

    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.

    Type CleanUp

    -

    Type CLEANUP

    +

    Type CLEANUP_AIRBASE

    +

    Field(s)

    +
    +
    + + +CLEANUP_AIRBASE:AddAirbase(AirbaseName) + +
    +
    + +

    Adds an airbase to the airbase validation list.

    + +

    Parameter

    +
      +
    • -

      The CLEANUP class.

      +

      #string AirbaseName :

      -

      Field(s)

      -
      -
      - - #string - -CLEANUP.ClassName - -
      -
      - +
    • +
    +

    Return value

    + +

    #CLEANUP_AIRBASE:

    @@ -260,36 +291,8 @@
    - - -CLEANUP.CleanUpList - -
    -
    - - - -
    -
    -
    -
    - - - -CLEANUP.CleanUpScheduler - -
    -
    - - - -
    -
    -
    -
    - - -CLEANUP:New(ZoneNames, TimeInterval) + +CLEANUP_AIRBASE:New(<, AirbaseNames)
    @@ -300,41 +303,87 @@
    • -

      #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.

      +

      #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.

    • -

      #number TimeInterval : -The interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes.

      +

      AirbaseNames :

    Return value

    -

    #CLEANUP:

    +

    #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 )
    +CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi ) +CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )

    - - -CLEANUP.TimeInterval + +CLEANUP_AIRBASE:RemoveAirbase(AirbaseName)
    +

    Removes an airbase from the airbase validation list.

    + +

    Parameter

    +
      +
    • + +

      #string AirbaseName :

      + +
    • +
    +

    Return value

    + +

    #CLEANUP_AIRBASE:

    + + +
    +
    +
    +
    + + +CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles) + +
    +
    + +

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

    + +

    Parameter

    +
      +
    • + +

      #string CleanMissiles : +(Default=true) If true, missiles fired are immediately destroyed. If false missiles are not controlled.

      + +
    • +
    +

    Return value

    + +

    #CLEANUP_AIRBASE:

    @@ -343,276 +392,43 @@ CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 )
    - -CLEANUP.ZoneNames + +CLEANUP_AIRBASE.__
    + +

    @field #CLEANUPAIRBASE._

    +
    + +

    Type CLEANUP_AIRBASE.__

    +

    Field(s)

    - -CLEANUP:_AddForCleanUp(CleanUpUnit, CleanUpUnitName) + #map + +CLEANUP_AIRBASE.__.<
    -

    Add the DCSWrapper.Unit#Unit to the CleanUpList for CleanUp.

    - -

    Parameters

    -
      -
    • - -

      CleanUpUnit :

      - -
    • -
    • - -

      CleanUpUnitName :

      - -
    • -
    -
    -
    -
    -
    - - -CLEANUP:_CleanUpScheduler() - -
    -
    - -

    At the defined time interval, CleanUp the Groups within the CleanUpList.

    +

    string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases.

    -
    -
    -
    -
    - - -CLEANUP:_DestroyGroup(GroupObject, CleanUpGroupName) - -
    -
    - -

    Destroys a group from the simulator, but checks first if it is still existing!

    - -

    Parameters

    -
      -
    • - -

      Dcs.DCSWrapper.Group#Group GroupObject : -The object to be destroyed.

      - -
    • -
    • - -

      #string CleanUpGroupName : -The groupname...

      - -
    • -
    -
    -
    -
    -
    - - -CLEANUP:_DestroyMissile(MissileObject) - -
    -
    - - - - -

    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

    - -

    Parameter

    -
      -
    • - -

      MissileObject :

      - -
    • -
    -
    -
    -
    -
    - - -CLEANUP:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - -
    -
    - -

    Destroys a DCSWrapper.Unit#Unit from the simulator, but checks first if it is still existing!

    - -

    Parameters

    -
      -
    • - -

      Dcs.DCSWrapper.Unit#Unit CleanUpUnit : -The object to be destroyed.

      - -
    • -
    • - -

      #string CleanUpUnitName : -The Unit name ...

      - -
    • -
    -
    -
    -
    -
    - - -CLEANUP:_EventAddForCleanUp(event, Event) - -
    -
    - -

    Detects if the Unit has an SEVENTENGINESHUTDOWN or an SEVENT_HIT within the given ZoneNames.

    - - -

    If this is the case, add the Group to the CLEANUP List.

    - -

    Parameters

    - -
    -
    -
    -
    - - -CLEANUP:_EventCrash(event, Event) - -
    -
    - -

    Detects if a crash event occurs.

    - - -

    Crashed units go into a CleanUpList for removal.

    - -

    Parameters

    - -
    -
    -
    -
    - - -CLEANUP:_EventHitCleanUp(event, Event) - -
    -
    - -

    Detects if the Unit has an SEVENTHIT within the given ZoneNames.

    - - -

    If this is the case, destroy the unit.

    - -

    Parameters

    - -
    -
    -
    -
    - - -CLEANUP:_EventShot(event, Event) - -
    -
    - -

    Detects if a unit shoots a missile.

    - - -

    If this occurs within one of the zones, then the weapon used must be destroyed.

    - -

    Parameters

    - -
    -
    -
    -
    - - -CLEANUP:_OnEventBirth(EventData) - -
    -
    - - - -

    Parameter

    -
    +

    Type CLEANUP_AIRBASE.__.Airbases

    + +

    Type list

    + +

    Type map

    + diff --git a/docs/Documentation/Client.html b/docs/Documentation/Client.html index 357ccad2a..4176d7720 100644 --- a/docs/Documentation/Client.html +++ b/docs/Documentation/Client.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/CommandCenter.html b/docs/Documentation/CommandCenter.html index f6099f06c..67b9ecc2d 100644 --- a/docs/Documentation/CommandCenter.html +++ b/docs/Documentation/CommandCenter.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -127,12 +138,6 @@

    COMMANDCENTER class, extends Base#BASE

    The COMMANDCENTER class governs multiple missions, the tasking and the reporting.

    - - - - REPORT - - @@ -217,9 +222,21 @@ - COMMANDCENTER:MessageToGroup(Message, TaskGroup, Name) + COMMANDCENTER:MessageToGroup(Message, TaskGroup)

    Send a CC message to a GROUP.

    + + + + COMMANDCENTER:MessageTypeToCoalition(Message, MessageType) + +

    Send a CC message of a specified type to the coalition of the CC.

    + + + + COMMANDCENTER:MessageTypeToGroup(Message, TaskGroup, MessageType) + +

    Send a CC message of a specified type to a GROUP.

    @@ -270,70 +287,6 @@ and will be replaced by a navigation using Reference Zones.

    COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix)

    Set special Reference Zones known by the Command Center to guide airborne pilots during WWII.

    - - - - -

    Type REPORT

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    REPORT:Add(Text) -

    Add a new line to a REPORT.

    -
    REPORT:AddIndent(Text) -

    Add a new line to a REPORT.

    -
    REPORT.ClassName - -
    REPORT:HasText() -

    Has the REPORT Text?

    -
    REPORT.Indent - -
    REPORT:New(Title) -

    Create a new REPORT.

    -
    REPORT.Report - -
    REPORT:SetIndent(Indent) -

    Set indent of a REPORT.

    -
    REPORT:Text(Delimiter) -

    Produces the text of the report, taking into account an optional delimeter, which is \n by default.

    -
    REPORT.Title -
    @@ -402,20 +355,6 @@ This is done by using the method C For the moment, only one Reference Zone class can be specified, but in the future, more classes will become possible.

    - - -
    -
    - - #REPORT - -REPORT - -
    -
    - - -

    Type CommandCenter

    @@ -674,7 +613,7 @@ true if in WWII mode.

    -COMMANDCENTER:MessageToGroup(Message, TaskGroup, Name) +COMMANDCENTER:MessageToGroup(Message, TaskGroup)
    @@ -693,10 +632,64 @@ true if in WWII mode.

    Wrapper.Group#GROUP TaskGroup :

    + +
    + +
    +
    + + +COMMANDCENTER:MessageTypeToCoalition(Message, MessageType) + +
    +
    + +

    Send a CC message of a specified type to the coalition of the CC.

    + +

    Parameters

    +
    • -

      #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.

      +

      #string Message : +The message.

      + +
    • +
    • + +

      Core.Message#MESSAGE.MessageType MessageType : +The type of the message, resulting in automatic time duration and prefix of the message.

      + +
    • +
    +
    +
    +
    +
    + + +COMMANDCENTER:MessageTypeToGroup(Message, TaskGroup, MessageType) + +
    +
    + +

    Send a CC message of a specified type to a GROUP.

    + +

    Parameters

    + @@ -925,217 +918,6 @@ The name before the #-mark indicating the class of the Reference Zones.

    #COMMANDCENTER:

    -
    -
    - -

    Type REPORT

    - -

    The REPORT class

    - -

    Field(s)

    -
    -
    - - -REPORT:Add(Text) - -
    -
    - -

    Add a new line to a REPORT.

    - -

    Parameter

    -
      -
    • - -

      #string Text :

      - -
    • -
    -

    Return value

    - -

    #REPORT:

    - - -
    -
    -
    -
    - - -REPORT:AddIndent(Text) - -
    -
    - -

    Add a new line to a REPORT.

    - -

    Parameter

    -
      -
    • - -

      #string Text :

      - -
    • -
    -

    Return value

    - -

    #REPORT:

    - - -
    -
    -
    -
    - - #string - -REPORT.ClassName - -
    -
    - - - -
    -
    -
    -
    - - -REPORT:HasText() - -
    -
    - -

    Has the REPORT Text?

    - -

    Return value

    - -

    #boolean:

    - - -
    -
    -
    -
    - - - -REPORT.Indent - -
    -
    - - - -
    -
    -
    -
    - - -REPORT:New(Title) - -
    -
    - -

    Create a new REPORT.

    - -

    Parameter

    -
      -
    • - -

      #string Title :

      - -
    • -
    -

    Return value

    - -

    #REPORT:

    - - -
    -
    -
    -
    - - - -REPORT.Report - -
    -
    - - - -
    -
    -
    -
    - - -REPORT:SetIndent(Indent) - -
    -
    - -

    Set indent of a REPORT.

    - -

    Parameter

    -
      -
    • - -

      #number Indent :

      - -
    • -
    -

    Return value

    - -

    #REPORT:

    - - -
    -
    -
    -
    - - -REPORT:Text(Delimiter) - -
    -
    - -

    Produces the text of the report, taking into account an optional delimeter, which is \n by default.

    - -

    Parameter

    -
      -
    • - -

      #string Delimiter : -(optional) A delimiter text.

      - -
    • -
    -

    Return value

    - -

    #string: -The report text.

    - -
    -
    -
    -
    - - - -REPORT.Title - -
    -
    - - -
    @@ -1143,8 +925,6 @@ The report text.

    Type list

    -

    Type sring

    - diff --git a/docs/Documentation/Controllable.html b/docs/Documentation/Controllable.html index 30e42f2ca..4685c619d 100644 --- a/docs/Documentation/Controllable.html +++ b/docs/Documentation/Controllable.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -235,6 +246,12 @@ CONTROLLABLE:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK)

    Return the detected targets of the controllable.

    + + + + CONTROLLABLE:GetFuel() + +

    Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks.

    @@ -247,6 +264,12 @@ CONTROLLABLE:GetLife0()

    Returns the initial health.

    + + + + CONTROLLABLE:GetSize() + + @@ -295,6 +318,24 @@ CONTROLLABLE:New(ControllableName)

    Create a new CONTROLLABLE from a DCSControllable

    + + + + CONTROLLABLE:OptionAlarmStateAuto() + +

    Alarm state to Auto: AI will automatically switch alarm states based on the presence of threats.

    + + + + CONTROLLABLE:OptionAlarmStateGreen() + +

    Alarm state to Green: Group is not combat ready.

    + + + + CONTROLLABLE:OptionAlarmStateRed() + +

    Alarm state to Red: Group is combat ready and actively searching for targets.

    @@ -403,6 +444,24 @@ CONTROLLABLE:OptionRTBBingoFuel(RTB)

    Set RTB on bingo fuel.

    + + + + CONTROLLABLE:PatrolRoute() + +

    (GROUND) Patrol iteratively using the waypoints the for the (parent) group.

    + + + + CONTROLLABLE:PatrolRouteRandom(Speed, Formation, ToWaypoint) + +

    (GROUND) Patrol randomly to the waypoints the for the (parent) group.

    + + + + CONTROLLABLE:PatrolZones(ZoneList, Speed, Formation) + +

    (GROUND) Patrol randomly to the waypoints the for the (parent) group.

    @@ -418,9 +477,21 @@ - CONTROLLABLE:Route(GoPoints) + CONTROLLABLE:Route(Route, DelaySeconds)

    Make the controllable to follow a given route.

    + + + + CONTROLLABLE:RouteAirTo(ToCoordinate, AltType, Type, Action, Speed, DelaySeconds) + +

    Make the AIR Controllable fly towards a specific point.

    + + + + CONTROLLABLE:RouteGroundTo(ToCoordinate, Speed, Formation, DelaySeconds) + +

    Make the GROUND Controllable to drive towards a specific point.

    @@ -445,6 +516,12 @@ CONTROLLABLE:SetTask(DCSTask, WaitTime)

    Clearing the Task Queue and Setting the Task on the queue from the controllable.

    + + + + CONTROLLABLE:SetTaskWaypoint(Waypoint, Task) + +

    Set a Task at a Waypoint using a Route list.

    @@ -532,9 +609,9 @@ - CONTROLLABLE:TaskFunction(WayPoint, WayPointIndex, FunctionString, FunctionArguments) + CONTROLLABLE:TaskFunction(FunctionString, ...) - +

    This creates a Task element, with an action to call a function as part of a Wrapped Task.

    @@ -763,6 +840,22 @@ This is different from the EnRoute tasks, where the targets of the task need to
  • 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

    + +

    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:

    @@ -780,7 +873,17 @@ This is different from the EnRoute tasks, where the targets of the task need to
  • 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:

    + + + +

    Option methods

    Controllable Option methods change the behaviour of the Controllable while being alive.

    @@ -1472,6 +1575,27 @@ DetectedTargets

    + +CONTROLLABLE:GetFuel() + +
    +
    + +

    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.

    + +

    Return value

    + +

    #nil: +The CONTROLLABLE is not existing or alive.

    + +
    +
    +
    +
    + CONTROLLABLE:GetLife() @@ -1526,6 +1650,19 @@ The controllable is not existing or alive.

    + +
    +
    +
    + + +CONTROLLABLE:GetSize() + +
    +
    + + +
    @@ -1721,6 +1858,66 @@ self

    + +CONTROLLABLE:OptionAlarmStateAuto() + +
    +
    + +

    Alarm state to Auto: AI will automatically switch alarm states based on the presence of threats.

    + + +

    The AI kind of cheats in this regard.

    + +

    Return value

    + +

    #CONTROLLABLE: +self

    + +
    +
    +
    +
    + + +CONTROLLABLE:OptionAlarmStateGreen() + +
    +
    + +

    Alarm state to Green: Group is not combat ready.

    + + +

    Sensors are stowed if possible.

    + +

    Return value

    + +

    #CONTROLLABLE: +self

    + +
    +
    +
    +
    + + +CONTROLLABLE:OptionAlarmStateRed() + +
    +
    + +

    Alarm state to Red: Group is combat ready and actively searching for targets.

    + +

    Return value

    + +

    #CONTROLLABLE: +self

    + +
    +
    +
    +
    + CONTROLLABLE:OptionROEHoldFire() @@ -2059,6 +2256,102 @@ Warning! When you switch this option off, the airborne group will continue to fl

    #CONTROLLABLE: self

    + +
    +
    +
    + + +CONTROLLABLE:PatrolRoute() + +
    +
    + +

    (GROUND) Patrol iteratively using the waypoints the for the (parent) group.

    + +

    Return value

    + +

    #CONTROLLABLE:

    + + +
    +
    +
    +
    + + +CONTROLLABLE:PatrolRouteRandom(Speed, Formation, ToWaypoint) + +
    +
    + +

    (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.

    + +

    Parameters

    +
      +
    • + +

      Speed :

      + +
    • +
    • + +

      Formation :

      + +
    • +
    • + +

      ToWaypoint :

      + +
    • +
    +

    Return value

    + +

    #CONTROLLABLE:

    + + +
    +
    +
    +
    + + +CONTROLLABLE:PatrolZones(ZoneList, Speed, Formation) + +
    +
    + +

    (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.

    + +

    Parameters

    +
      +
    • + +

      ZoneList :

      + +
    • +
    • + +

      Speed :

      + +
    • +
    • + +

      Formation :

      + +
    • +
    +

    Return value

    + +

    #CONTROLLABLE:

    + +
    @@ -2114,26 +2407,134 @@ self

    -CONTROLLABLE:Route(GoPoints) +CONTROLLABLE:Route(Route, DelaySeconds)

    Make the controllable to follow a given route.

    -

    Parameter

    +

    Parameters

    • -

      #table GoPoints : +

      #table Route : A table of Route Points.

      +
    • +
    • + +

      #number DelaySeconds : +Wait for the specified seconds before executing the Route.

      +

    Return value

    #CONTROLLABLE: -self

    +The CONTROLLABLE.

    + +
    +
    +
    +
    + + +CONTROLLABLE:RouteAirTo(ToCoordinate, AltType, Type, Action, Speed, DelaySeconds) + +
    +
    + +

    Make the AIR Controllable fly towards a specific point.

    + +

    Parameters

    + +

    Return value

    + +

    #CONTROLLABLE: +The CONTROLLABLE.

    + +
    +
    +
    +
    + + +CONTROLLABLE:RouteGroundTo(ToCoordinate, Speed, Formation, DelaySeconds) + +
    +
    + +

    Make the GROUND Controllable to drive towards a specific point.

    + +

    Parameters

    +
      +
    • + +

      Core.Point#COORDINATE ToCoordinate : +A Coordinate to drive to.

      + +
    • +
    • + +

      #number Speed : +(optional) Speed in km/h. The default speed is 999 km/h.

      + +
    • +
    • + +

      #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".

      + +
    • +
    • + +

      #number DelaySeconds : +Wait for the specified seconds before executing the Route.

      + +
    • +
    +

    Return value

    + +

    #CONTROLLABLE: +The CONTROLLABLE.

    @@ -2258,6 +2659,39 @@ self

    Wrapper.Controllable#CONTROLLABLE: self

    + +
    +
    +
    + + +CONTROLLABLE:SetTaskWaypoint(Waypoint, Task) + +
    +
    + +

    Set a Task at a Waypoint using a Route list.

    + +

    Parameters

    +
      +
    • + +

      #table Waypoint : +The Waypoint!

      + +
    • +
    • + +

      Dcs.DCSTasking.Task#Task Task : +The Task structure to be executed!

      + +
    • +
    +

    Return value

    + +

    Dcs.DCSTasking.Task#Task:

    + +
    @@ -2952,36 +3386,79 @@ The DCS task structure.

    -CONTROLLABLE:TaskFunction(WayPoint, WayPointIndex, FunctionString, FunctionArguments) +CONTROLLABLE:TaskFunction(FunctionString, ...)
    +

    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.

    Parameters

    • -

      WayPoint :

      +

      #string FunctionString : +The function name embedded as a string that will be called.

    • -

      WayPointIndex :

      - -
    • -
    • - -

      FunctionString :

      - -
    • -
    • - -

      FunctionArguments :

      +

      ... : +The variable arguments passed to the function when called! These arguments can be of any type!

    +

    Return value

    + +

    #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] )
    +
    +
    diff --git a/docs/Documentation/DCSAirbase.html b/docs/Documentation/DCSAirbase.html index dcf1f5e56..d3aa90a32 100644 --- a/docs/Documentation/DCSAirbase.html +++ b/docs/Documentation/DCSAirbase.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSCoalitionObject.html b/docs/Documentation/DCSCoalitionObject.html index 4a748c821..cb2fe508a 100644 --- a/docs/Documentation/DCSCoalitionObject.html +++ b/docs/Documentation/DCSCoalitionObject.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSCommand.html b/docs/Documentation/DCSCommand.html index 0d24f3eea..c2776fc3f 100644 --- a/docs/Documentation/DCSCommand.html +++ b/docs/Documentation/DCSCommand.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSController.html b/docs/Documentation/DCSController.html index e9d27f273..74d16e392 100644 --- a/docs/Documentation/DCSController.html +++ b/docs/Documentation/DCSController.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSGroup.html b/docs/Documentation/DCSGroup.html index d2d97a479..4d9c33b69 100644 --- a/docs/Documentation/DCSGroup.html +++ b/docs/Documentation/DCSGroup.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSObject.html b/docs/Documentation/DCSObject.html index 5d492f76a..8d3b72e39 100644 --- a/docs/Documentation/DCSObject.html +++ b/docs/Documentation/DCSObject.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSTask.html b/docs/Documentation/DCSTask.html index 38a572b55..bbf5baa91 100644 --- a/docs/Documentation/DCSTask.html +++ b/docs/Documentation/DCSTask.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSTypes.html b/docs/Documentation/DCSTypes.html index fb39414e4..a336575bf 100644 --- a/docs/Documentation/DCSTypes.html +++ b/docs/Documentation/DCSTypes.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSUnit.html b/docs/Documentation/DCSUnit.html index a1a88b247..6cf730ebf 100644 --- a/docs/Documentation/DCSUnit.html +++ b/docs/Documentation/DCSUnit.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSVec3.html b/docs/Documentation/DCSVec3.html index e02fde38d..875ba7bf9 100644 --- a/docs/Documentation/DCSVec3.html +++ b/docs/Documentation/DCSVec3.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSWorld.html b/docs/Documentation/DCSWorld.html index a7269c14c..b730818f6 100644 --- a/docs/Documentation/DCSWorld.html +++ b/docs/Documentation/DCSWorld.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCSZone.html b/docs/Documentation/DCSZone.html index e659fd90f..c3b6be5e4 100644 --- a/docs/Documentation/DCSZone.html +++ b/docs/Documentation/DCSZone.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCScountry.html b/docs/Documentation/DCScountry.html index 799258ab2..79609db55 100644 --- a/docs/Documentation/DCScountry.html +++ b/docs/Documentation/DCScountry.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCStimer.html b/docs/Documentation/DCStimer.html index 6063a6ed1..f0dabbc94 100644 --- a/docs/Documentation/DCStimer.html +++ b/docs/Documentation/DCStimer.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/DCStrigger.html b/docs/Documentation/DCStrigger.html index 363311917..fb1d038f7 100644 --- a/docs/Documentation/DCStrigger.html +++ b/docs/Documentation/DCStrigger.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Database.html b/docs/Documentation/Database.html index 85a5a5865..80ad67e61 100644 --- a/docs/Documentation/Database.html +++ b/docs/Documentation/Database.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -111,7 +122,19 @@
    -

    1) #DATABASE class, extends Base#BASE

    +

    Author: Sven Van de Velde (FlightControl)

    +

    Contributions:

    + +
    + + +

    Global(s)

    + + + + + + + + @@ -342,6 +359,12 @@ + + + + @@ -375,7 +398,7 @@ - + @@ -438,6 +461,12 @@ + + + + @@ -449,7 +478,7 @@ @@ -912,21 +941,11 @@ The coalition side of the DCS Group.

    Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission.

    -

    Return values

    -
      -
    1. +

      Return value

      Core.Point#COORDINATE: The COORDINATE of the GROUP.

      -
    2. -
    3. - -

      #nil: -The POSITIONABLE is not existing or alive.

      - -
    4. -
    @@ -1026,6 +1045,37 @@ The DCS Units.

    + +GROUP:GetFuel() + +
    +
    + +

    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.

    + +

    Return values

    +
      +
    1. + +

      #number: +The relative amount of fuel (from 0.0 to 1.0).

      + +
    2. +
    3. + +

      #nil: +The GROUP is not existing or alive.

      + +
    4. +
    +
    +
    +
    +
    + GROUP:GetHeading() @@ -1352,6 +1402,24 @@ The mission route defined by points.

    #table:

    + +
    +
    +
    + + +GROUP:GetTemplateRoutePoints() + +
    +
    + +

    Returns the group template route.points[] (the waypoints) from the DATABASE (_DATABASE object).

    + +

    Return value

    + +

    #table:

    + +
    @@ -1456,7 +1524,7 @@ Current Vec3 of the first DCS Unit of the GROUP.

    -GROUP:HandleEvent(Event, EventFunction) +GROUP:HandleEvent(Event, EventFunction, ...)
    @@ -1475,6 +1543,11 @@ Current Vec3 of the first DCS Unit of the GROUP.

    #function EventFunction : (optional) The function to be called when the event occurs for the GROUP.

    + +
  • + +

    ... :

    +
  • Return value

    @@ -1735,6 +1808,55 @@ true if DCS Group contains Ships.

    + +GROUP:NewTemplate(GroupTemplate, CoalitionSide, CategoryID, CountryID) + +
    +
    + +

    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.

    + +

    Parameters

    + +

    Return value

    + +

    #GROUP: +self

    + +
    +
    +
    +
    + GROUP:OnReSpawn(ReSpawnFunction) @@ -1762,14 +1884,14 @@ true if DCS Group contains Ships.

    -

    Create a new GROUP from a DCSGroup

    +

    Create a new GROUP from an existing Group in the Mission.

    Parameter

    diff --git a/docs/Documentation/Identifiable.html b/docs/Documentation/Identifiable.html index bb4e93bce..b108c3bfe 100644 --- a/docs/Documentation/Identifiable.html +++ b/docs/Documentation/Identifiable.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -157,6 +168,12 @@
    + + + + @@ -334,6 +351,34 @@ The DCS Identifiable is not existing or alive.

    + +IDENTIFIABLE:GetCoalitionName() + +
    +
    + +

    Returns the name of the coalition of the Identifiable.

    + +

    Return values

    +
      +
    1. + +

      #string: +The name of the coalition.

      + +
    2. +
    3. + +

      #nil: +The DCS Identifiable is not existing or alive.

      + +
    4. +
    +
    +
    +
    +
    + IDENTIFIABLE:GetCountry() diff --git a/docs/Documentation/Menu.html b/docs/Documentation/Menu.html index cda35ef3a..aaed17afa 100644 --- a/docs/Documentation/Menu.html +++ b/docs/Documentation/Menu.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -194,6 +205,7 @@ On top, MOOSE implements variable parameter passing for command

    The MENUCOMMANDBASE class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands.

    +
    @@ -241,6 +253,12 @@ classes are derived from, in order to set commands.

    + + + + @@ -259,6 +277,12 @@ classes are derived from, in order to set commands.

    + + + + @@ -357,6 +381,24 @@ classes are derived from, in order to set commands.

    + + + + + + + +
    DATABASE +

    DATABASE class, extends Base#BASE

    +

    Mission designers can use the DATABASE class to refer to:

      @@ -126,38 +149,6 @@

    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.

    - -

    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:

    - - - -
    - - -

    Author: Sven Van de Velde (FlightControl)

    -

    Contributions:

    - -
    - -

    Global(s)

    - - - - @@ -170,9 +161,15 @@ The following iterator methods are currently available within the DATABASE:

    Type DATABASE

    DATABASE -
    - + + + + + @@ -215,36 +212,6 @@ The following iterator methods are currently available within the DATABASE:

    - - - - - - - - - - - - - - - - - - - - @@ -280,7 +247,7 @@ The following iterator methods are currently available within the DATABASE:

    @@ -365,12 +332,6 @@ The following iterator methods are currently available within the DATABASE:

    - - - - @@ -437,12 +398,6 @@ The following iterator methods are currently available within the DATABASE:

    - - - - @@ -467,36 +422,6 @@ The following iterator methods are currently available within the DATABASE:

    - - - - - - - - - - - - - - - - - - - - @@ -515,36 +440,18 @@ The following iterator methods are currently available within the DATABASE:

    - - - - - - - - - - - - @@ -584,7 +491,7 @@ The following iterator methods are currently available within the DATABASE:

    - + @@ -632,6 +539,27 @@ The following iterator methods are currently available within the DATABASE:

    +

    DATABASE class, extends Base#BASE

    + +

    Mission designers can use the DATABASE class to refer to:

    + +
      +
    • STATICS
    • +
    • UNITS
    • +
    • GROUPS
    • +
    • CLIENTS
    • +
    • AIRBASES
    • +
    • PLAYERSJOINED
    • +
    • PLAYERS
    • +
    • CARGOS
    • +
    + +

    On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.

    + + + +

    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.

    @@ -653,22 +581,47 @@ The following iterator methods are currently available within the DATABASE:

    Type Database

    Type DATABASE

    - -

    DATABASE class

    - -

    Field(s)

    +

    Field(s)

    - - -DATABASE.AIRBASES + +DATABASE:AccountDestroys(Event)
    +

    Account the destroys.

    +

    Parameter

    + +
    +
    +
    +
    + + +DATABASE:AccountHits(Event) + +
    +
    + +

    Account the Hits of the Players.

    + +

    Parameter

    +
    @@ -828,76 +781,6 @@ The name of the airbase

    - -
    -
    -
    - - - -DATABASE.CARGOS - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.CLIENTS - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.COUNTRY_ID - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.COUNTRY_NAME - -
    -
    - - - -
    -
    -
    -
    - - #string - -DATABASE.ClassName - -
    -
    - - -
    @@ -1021,7 +904,7 @@ The name of the airbase

    -

    Finds an AIRBASE based on the AirbaseName.

    +

    Finds a AIRBASE based on the AirbaseName.

    Parameter

      @@ -1494,20 +1377,6 @@ The function that will be called for each object in the database. The function n

      #DATABASE: self

      -
    -
    -
    -
    - - - -DATABASE.GROUPS - -
    -
    - - -
    @@ -1744,20 +1613,6 @@ self

    - -
    -
    -
    - - - -DATABASE.NavPoints - -
    -
    - - -
    @@ -1836,76 +1691,6 @@ DBObject = DATABASE:New() - -
    -
    -
    - - - -DATABASE.PLAYERS - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.PLAYERSETTINGS - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.PLAYERSJOINED - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.PLAYERUNITS - -
    -
    - - - -
    -
    -
    -
    - - - -DATABASE.STATICS - -
    -
    - - -
    @@ -1994,20 +1779,6 @@ This method is used by the SPAWN class.

    #DATABASE: self

    - -
    -
    -
    - - - -DATABASE.Templates - -
    -
    - - -
    @@ -2022,20 +1793,6 @@ self

    - -
    -
    -
    - - - -DATABASE.UNITS_Index - -
    -
    - - -
    @@ -2050,20 +1807,6 @@ self

    - -
    -
    -
    - - - -DATABASE.ZONENAMES - -
    -
    - - -
    @@ -2185,7 +1928,7 @@ self

    -DATABASE:_RegisterGroupTemplate(GroupTemplate, CoalitionID, CategoryID, CountryID) +DATABASE:_RegisterGroupTemplate(GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName)
    @@ -2201,17 +1944,25 @@ self

  • -

    CoalitionID :

    +

    Dcs.DCScoalition#coalition.side CoalitionSide : +The coalition.side of the object.

  • -

    CategoryID :

    +

    Dcs.DCSObject#Object.Category CategoryID : +The Object.category of the object.

  • -

    CountryID :

    +

    Dcs.DCScountry#country.id CountryID : +the country.id of the object

    + +
  • +
  • + +

    GroupName :

  • diff --git a/docs/Documentation/Designate.html b/docs/Documentation/Designate.html index c0c7ee573..03e24e109 100644 --- a/docs/Documentation/Designate.html +++ b/docs/Documentation/Designate.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -108,6 +119,7 @@

    Functional -- Management of target Designation.

    +

    Lase, smoke and illuminate targets.

    --Banner Image

    @@ -163,9 +175,9 @@ each detected set of potential targets can be lased or smoked...

    Type DESIGNATE

    DATABASE.AIRBASESDATABASE:AccountDestroys(Event) - +

    Account the destroys.

    +
    DATABASE:AccountHits(Event) +

    Account the Hits of the Players.

    DATABASE:AddUnit(DCSUnitName)

    Adds a Unit based on the Unit Name in the DATABASE.

    -
    DATABASE.CARGOS - -
    DATABASE.CLIENTS - -
    DATABASE.COUNTRY_ID - -
    DATABASE.COUNTRY_NAME - -
    DATABASE.ClassName -
    DATABASE:FindAirbase(AirbaseName) -

    Finds an AIRBASE based on the AirbaseName.

    +

    Finds a AIRBASE based on the AirbaseName.

    DATABASE:ForEachUnit(IteratorFunction, FinalizeFunction, ...)

    Iterate the DATABASE and call an iterator function for each alive UNIT, providing the UNIT and optional parameters.

    -
    DATABASE.GROUPS -
    DATABASE:GetStatusGroup(GroupName)

    Get a status to a Group within the Database, this to check crossing events for example.

    -
    DATABASE.NavPoints -
    DATABASE:OnEventNewCargo(EventData)

    Handles the OnEventNewCargo event.

    -
    DATABASE.PLAYERS - -
    DATABASE.PLAYERSETTINGS - -
    DATABASE.PLAYERSJOINED - -
    DATABASE.PLAYERUNITS - -
    DATABASE.STATICS -
    DATABASE:Spawn(SpawnTemplate)

    Instantiate new Groups within the DCSRTE.

    -
    DATABASE.Templates -
    DATABASE.UNITS -
    DATABASE.UNITS_Index -
    DATABASE.UNITS_Position -
    DATABASE.ZONENAMES -
    DATABASE:_RegisterGroupTemplate(GroupTemplate, CoalitionID, CategoryID, CountryID)DATABASE:_RegisterGroupTemplate(GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName)

    Private method that registers new Group Templates within the DATABASE Object.

    - + @@ -184,12 +196,30 @@ each detected set of potential targets can be lased or smoked...

    + + + + + + + + + + + + @@ -238,6 +268,12 @@ each detected set of potential targets can be lased or smoked...

    + + + + @@ -250,24 +286,78 @@ each detected set of potential targets can be lased or smoked...

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -280,6 +370,12 @@ each detected set of potential targets can be lased or smoked...

    + + + + @@ -295,7 +391,13 @@ each detected set of potential targets can be lased or smoked...

    - + + + + + @@ -382,6 +484,12 @@ each detected set of potential targets can be lased or smoked...

    + + + + @@ -391,7 +499,7 @@ each detected set of potential targets can be lased or smoked...

    - + @@ -400,6 +508,12 @@ each detected set of potential targets can be lased or smoked...

    + + + + @@ -412,6 +526,42 @@ each detected set of potential targets can be lased or smoked...

    + + + + + + + + + + + + + + + + + + + + + + + + @@ -499,13 +649,13 @@ each detected set of potential targets can be lased or smoked...

    - + - + @@ -567,11 +717,15 @@ each detected set of potential targets can be lased or smoked...

    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.

    @@ -615,7 +769,7 @@ Once Targets have been detected, the DesignateObject will trigger the De

    2.1 DESIGNATE States

      -
    • Designating ( Group ): The process is not started yet.
    • +
    • Designating ( Group ): The designation process.

    2.2 DESIGNATE Events

    @@ -628,9 +782,17 @@ Once Targets have been detected, the DesignateObject will trigger the De
  • **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. @@ -650,11 +812,20 @@ One laser code can be given or an sequence of laser codes through an table...

    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. @@ -666,7 +837,7 @@ Note that autolase will automatically activate lasing for ALL DetectedItems. Ind

    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, @@ -680,7 +851,12 @@ If not activated, Targets will be selected in a random order, but most like thos

    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.

    @@ -706,19 +882,42 @@ If not activated, Targets will be selected in a random order, but most like thos
    - -DESIGNATE:ActivateAutoLase() + +DESIGNATE:AddMenuLaserCode(LaserCode, MenuText)
    -

    Coordinates the Auto Lase.

    +

    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.

    + +

    Parameters

    +
      +
    • + +

      #number LaserCode : +The specific laser code to be added to the lase menu.

      + +
    • +
    • + +

      #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 value

    #DESIGNATE:

    +

    Usage:

    +
      RecceDesignation:AddMenuLaserCode( 1113, "Lase with %d for Su-25T" )
    +  RecceDesignation:AddMenuLaserCode( 1680, "Lase with %d for A-10A" )
    +
    +
    @@ -738,7 +937,6 @@ If not activated, Targets will be selected in a random order, but most like thos
    - DESIGNATE.AutoLase @@ -761,6 +959,37 @@ If not activated, Targets will be selected in a random order, but most like thos + +
    +
    +
    + + +DESIGNATE:CoordinateLase() + +
    +
    + +

    Coordinates the Auto Lase.

    + +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE.DesignateName + +
    +
    + + +
    @@ -775,6 +1004,24 @@ If not activated, Targets will be selected in a random order, but most like thos + +
    +
    +
    + + +DESIGNATE:DesignationScope() + +
    +
    + +

    Adapt the designation scope according the detected items.

    + +

    Return value

    + +

    #DESIGNATE:

    + +
    @@ -860,7 +1107,7 @@ function below will use the range 1-7 just in case

    - #number + DESIGNATE.LaseDuration @@ -895,6 +1142,20 @@ function below will use the range 1-7 just in case

    LaseOn Trigger for DESIGNATE

    + +
    +
    +
    + + + +DESIGNATE.LaseStart + +
    +
    + + +
    @@ -922,6 +1183,90 @@ function below will use the range 1-7 just in case

    + +
    +
    +
    + + + +DESIGNATE.MarkScheduler + +
    +
    + + + +
    +
    +
    +
    + + + +DESIGNATE.MaximumDesignations + +
    +
    + + + +
    +
    +
    +
    + + + +DESIGNATE.MaximumDistanceAirDesignation + +
    +
    + + + +
    +
    +
    +
    + + + +DESIGNATE.MaximumDistanceDesignations + +
    +
    + + + +
    +
    +
    +
    + + + +DESIGNATE.MaximumDistanceGroundDesignation + +
    +
    + + + +
    +
    +
    +
    + + + +DESIGNATE.MaximumMarkings + +
    +
    + + +
    @@ -943,6 +1288,19 @@ function below will use the range 1-7 just in case

    + +
    +
    +
    + + +DESIGNATE.MenuDesignate + +
    +
    + + +
    @@ -974,6 +1332,27 @@ function below will use the range 1-7 just in case

    + +DESIGNATE:MenuForget(Index) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      Index :

      + +
    • +
    +
    +
    +
    +
    + DESIGNATE:MenuIlluminate(Index) @@ -995,6 +1374,37 @@ function below will use the range 1-7 just in case

    + +DESIGNATE:MenuLaseCode(Index, Duration, LaserCode) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      Index :

      + +
    • +
    • + +

      Duration :

      + +
    • +
    • + +

      LaserCode :

      + +
    • +
    +
    +
    +
    +
    + DESIGNATE:MenuLaseOff(Index, Duration) @@ -1042,6 +1452,23 @@ function below will use the range 1-7 just in case

    + +
    +
    +
    + + + +DESIGNATE.MenuLaserCodes + +
    +
    + + + + +

    This map contains the laser codes that will be shown in the designate menu to lase with specific laser codes.

    +
    @@ -1094,13 +1521,27 @@ function below will use the range 1-7 just in case

    + +
    +
    +
    + + + +DESIGNATE.Mission + +
    +
    + + +
    -DESIGNATE:New(CC, Detection, AttackSet) +DESIGNATE:New(CC, Detection, AttackSet, Mission)
    @@ -1127,6 +1568,12 @@ function below will use the range 1-7 just in case

    Core.Set#SET_GROUP AttackSet : The Attack collection of GROUP objects to designate and report for.

    + +
  • + +

    Tasking.Mission#MISSION Mission : +(Optional) The Mission where the menu needs to be attached.

    +
  • Return value

    @@ -1564,6 +2011,37 @@ The Attack collection of GROUP objects to designate and report for.

    +
    +
    +
    +
    + + +DESIGNATE:RemoveMenuLaserCode(LaserCode) + +
    +
    + +

    Removes a specific lase code from the designate lase menu.

    + +

    Parameter

    +
      +
    • + +

      #number LaserCode : +The specific laser code that was set to be added to the lase menu.

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +

    Usage:

    +
      RecceDesignation:RemoveMenuLaserCode( 1113 )
    +  
    +
    @@ -1607,7 +2085,7 @@ The time in seconds the report should be visible.

    -DESIGNATE:SetAutoLase(AutoLase) +DESIGNATE:SetAutoLase(AutoLase, Message)
    @@ -1617,11 +2095,18 @@ The time in seconds the report should be visible.

    Auto lase will start lasing targets immediately when these are in range.

    -

    Parameter

    +

    Parameters

    • -

      #boolean AutoLase :

      +

      #boolean AutoLase : +(optional) true sets autolase on, false off. Default is off.

      + +
    • +
    • + +

      #boolean Message : +(optional) true is send message, false or nil won't send a message. Default is no message sent.

    @@ -1648,6 +2133,36 @@ The time in seconds the report should be visible.

    #DESIGNATE:

    +
    +
    +
    +
    + + +DESIGNATE:SetDesignateName(DesignateName) + +
    +
    + +

    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.

    + +

    Parameter

    +
      +
    • + +

      #string DesignateName :

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + +
    @@ -1710,6 +2225,170 @@ number> LaserCodes

    #DESIGNATE:

    + +
    +
    +
    + + +DESIGNATE:SetMaximumDesignations(MaximumDesignations) + +
    +
    + +

    Set the maximum amount of designations.

    + +

    Parameter

    +
      +
    • + +

      #number MaximumDesignations :

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation) + +
    +
    + +

    Set the maximum air designation distance.

    + +

    Parameter

    +
      +
    • + +

      #number MaximumDistanceAirDesignation : +Maximum air designation distance in meters.

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations) + +
    +
    + +

    Set the overall maximum distance when designations can be accepted.

    + +

    Parameter

    +
      +
    • + +

      #number MaximumDistanceDesignations : +Maximum distance in meters to accept designations.

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation) + +
    +
    + +

    Set the maximum ground designation distance.

    + +

    Parameter

    +
      +
    • + +

      #number MaximumDistanceGroundDesignation : +Maximum ground designation distance in meters.

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE:SetMaximumMarkings(MaximumMarkings) + +
    +
    + +

    Set the maximum amount of markings FACs will do, per designated target group.

    + +

    Parameter

    +
      +
    • + +

      #number MaximumMarkings : +Maximum markings FACs will do, per designated target group.

      + +
    • +
    +

    Return value

    + +

    #DESIGNATE:

    + + +
    +
    +
    +
    + + +DESIGNATE:SetMission(Mission) + +
    +
    + +

    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.

    + +

    Parameter

    + +

    Return value

    + +

    #DESIGNATE:

    + +
    @@ -2049,7 +2728,7 @@ number> LaserCodes

    -DESIGNATE:onafterLaseOn(From, Event, To, Index, Duration) +DESIGNATE:onafterLaseOn(From, Event, To, Index, Duration, LaserCode)
    @@ -2082,6 +2761,11 @@ number> LaserCodes

    Duration :

    + +
  • + +

    LaserCode :

    +
  • @@ -2090,7 +2774,7 @@ number> LaserCodes

    -DESIGNATE:onafterLasing(From, Event, To, Index, Duration) +DESIGNATE:onafterLasing(From, Event, To, Index, Duration, LaserCodeRequested)
    @@ -2123,6 +2807,11 @@ number> LaserCodes

    Duration :

    + +
  • + +

    LaserCodeRequested :

    +
  • Return value

    diff --git a/docs/Documentation/Detection.html b/docs/Documentation/Detection.html index 141b7d951..570490cec 100644 --- a/docs/Documentation/Detection.html +++ b/docs/Documentation/Detection.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -187,15 +198,21 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu
    - + + + + + @@ -211,7 +228,7 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu - + @@ -250,12 +267,6 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu - - - - @@ -368,12 +379,6 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu - - - - @@ -449,7 +454,7 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu - + @@ -482,12 +487,6 @@ DETECTION uses the in-built detection capabilities of DCS World, but adds new fu - - - - @@ -535,18 +534,36 @@ The different values of Unit.Category can be:

    Multiple Unit.Category entries can be given as a table and then these will be evaluated as an OR expression.

    + +
    + + + + + + + + + + + @@ -564,13 +581,19 @@ The different values of Unit.Category can be:

    + + + + @@ -601,6 +624,12 @@ The different values of Unit.Category can be:

    + + + + @@ -612,13 +641,19 @@ The different values of Unit.Category can be:

    + + + + @@ -667,6 +702,18 @@ The different values of Unit.Category can be:

    + + + + + + + + @@ -685,6 +732,12 @@ The different values of Unit.Category can be:

    + + + + @@ -769,6 +822,12 @@ The different values of Unit.Category can be:

    + + + + @@ -826,9 +885,15 @@ The different values of Unit.Category can be:

    - + + + + + @@ -841,6 +906,24 @@ The different values of Unit.Category can be:

    + + + + + + + + + + + + @@ -947,6 +1030,18 @@ The different values of Unit.Category can be:

    + + + + + + + + @@ -956,13 +1051,13 @@ The different values of Unit.Category can be:

    - + - + @@ -1072,7 +1167,7 @@ The different values of Unit.Category can be:

    - + @@ -1154,7 +1249,7 @@ The different values of Unit.Category can be:

    - + @@ -1566,13 +1661,13 @@ self

    - -DETECTION_AREAS:CalculateThreatLevelA2G(DetectedItem) + +DETECTION_AREAS:CalculateIntercept(DetectedItem)
    -

    Calculate the maxium A2G threat level of the DetectedItem.

    +

    Calculate the optimal intercept point of the DetectedItem.

    Parameter

      @@ -1596,6 +1691,20 @@ self

      +
    +
    +
    +
    + + + +DETECTION_AREAS.CountryID + +
    +
    + + +
    @@ -1654,7 +1763,7 @@ self

    -DETECTION_AREAS:DetectedItemReportSummary(Index, AttackGroup) +DETECTION_AREAS:DetectedItemReportSummary(Index, AttackGroup, Settings)
    @@ -1670,14 +1779,21 @@ self

  • -

    AttackGroup :

    +

    Wrapper.Group#GROUP AttackGroup : +The group to get the settings for.

    + +
  • +
  • + +

    Core.Settings#SETTINGS Settings : +(Optional) Message formatting settings to use.

  • Return value

    -

    #string:

    - +

    Core.Report#REPORT: +The report of the detection items.

    @@ -1710,7 +1826,8 @@ self

    @@ -1800,32 +1917,6 @@ The Changes text

    - -DETECTION_AREAS:GetTreatLevelA2G(DetectedItem) - -
    -
    - -

    Returns the A2G threat level of the units in the DetectedItem

    - -

    Parameter

    - -

    Return value

    - -

    #number: -a scale from 0 to 10.

    - -
    -
    -
    -
    - DETECTION_AREAS:NearestFAC(DetectedItem) @@ -2243,20 +2334,6 @@ The index of the DetectedItem.

    - -
    -
    -
    - - - -DETECTION_BASE.CountryID - -
    -
    - - -
    @@ -2393,7 +2470,6 @@ The index of the DetectedItem.

    - #number DETECTION_BASE.DetectedItemCount @@ -2407,7 +2483,6 @@ The index of the DetectedItem.

    - #number DETECTION_BASE.DetectedItemMax @@ -2453,7 +2528,7 @@ The index of the DetectedItem.

    -DETECTION_BASE:DetectedItemReportSummary(Index, AttackGroup) +DETECTION_BASE:DetectedItemReportSummary(Index, AttackGroup, Settings)
    @@ -2469,13 +2544,20 @@ The index of the DetectedItem.

  • -

    AttackGroup :

    +

    Wrapper.Group#GROUP AttackGroup : +The group to generate the report for.

    + +
  • +
  • + +

    Core.Settings#SETTINGS Settings : +Message formatting settings to use.

  • Return value

    -

    #string:

    +

    Core.Report#REPORT:

    @@ -2537,7 +2619,8 @@ The index of the DetectedItem.

    @@ -2560,20 +2643,6 @@ The index of the DetectedItem.

    - -
    -
    -
    - - #number - -DETECTION_BASE.DetectionInterval - -
    -
    - - -
    @@ -2700,6 +2769,32 @@ cs.DCSUnit#Unit> FilterCategories The Categories entries

    #DETECTION_BASE: self

    + +
    +
    +
    + + +DETECTION_BASE:FilterFriendliesCategory(FriendliesCategory) + +
    +
    + +

    Filters friendly units by unit category.

    + +

    Parameter

    +
      +
    • + +

      FriendliesCategory :

      + +
    • +
    +

    Return value

    + +

    #DETECTION_BASE:

    + +
    @@ -2727,6 +2822,20 @@ The UnitName that needs to be forgotten from the DetectionItem Sets.

    #DETECTION_BASE:

    + +
    +
    +
    + + + +DETECTION_BASE.FriendliesCategory + +
    +
    + + +
    @@ -2741,6 +2850,19 @@ The UnitName that needs to be forgotten from the DetectionItem Sets.

    + +
    +
    +
    + + +DETECTION_BASE.FriendlyPrefixes + +
    +
    + + +
    @@ -2804,7 +2926,7 @@ DetectedItemID

    -

    Get the COORDINATE of a detection item using a given numeric index.

    +

    Get the detected item coordinate.

    Parameter

    +
    +
    +
    + + + +DETECTION_BASE.Intercept + +
    +
    + + + +
    +
    +
    +
    + + + +DETECTION_BASE.InterceptDelay + +
    +
    + + +
    @@ -3308,7 +3536,33 @@ true if already identified.

    Return value

    #boolean: -trhe if there are friendlies nearby

    +true if there are friendlies nearby

    + + +
    +
    +
    + + +DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem) + +
    +
    + +

    Returns if there are friendlies nearby the intercept ...

    + +

    Parameter

    +
      +
    • + +

      DetectedItem :

      + +
    • +
    +

    Return value

    + +

    #boolean: +trhe if there are friendlies near the intercept.

    @@ -3801,6 +4055,20 @@ The To State string.

    #boolean: Return false to cancel Transition.

    + +
    +
    +
    + + #number + +DETECTION_BASE.RefreshTimeInterval + +
    +
    + + +
    @@ -3966,7 +4234,7 @@ self

    Return value

    -

    #string:

    - +

    Core.Report#REPORT: +The report of the detection items.

    @@ -4867,7 +5317,8 @@ self

    @@ -5099,7 +5550,7 @@ self

    -DETECTION_UNITS:DetectedItemReportSummary(Index, AttackGroup) +DETECTION_UNITS:DetectedItemReportSummary(Index, AttackGroup, Settings)
    @@ -5115,14 +5566,21 @@ self

  • -

    AttackGroup :

    +

    Wrapper.Group#GROUP AttackGroup : +The group to generate the report for.

    + +
  • +
  • + +

    Core.Settings#SETTINGS Settings : +Message formatting settings to use.

  • Return value

    -

    #string:

    - +

    Core.Report#REPORT: +The report of the detection items.

    @@ -5141,7 +5599,8 @@ self

    diff --git a/docs/Documentation/DetectionManager.html b/docs/Documentation/DetectionManager.html index ffff150e6..479a1e818 100644 --- a/docs/Documentation/DetectionManager.html +++ b/docs/Documentation/DetectionManager.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -121,7 +132,7 @@ Reportings can be done in several manners, and it is up to the derived classes i

    1.2) DETECTION_MANAGER reporting:

    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.

    @@ -223,15 +234,15 @@ If an ad-hoc report is requested, use the method DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime) +
    - + @@ -247,13 +258,13 @@ If an ad-hoc report is requested, use the method DETECTION_MANAGER._ReportDisplayTime + - + @@ -614,6 +625,33 @@ self

    + +DETECTION_MANAGER:SetRefreshTimeInterval(RefreshTimeInterval) + +
    +
    + +

    Set the reporting time interval.

    + +

    Parameter

    +
      +
    • + +

      #number RefreshTimeInterval : +The interval in seconds when a report needs to be done.

      + +
    • +
    +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime) @@ -641,33 +679,6 @@ self

    - -DETECTION_MANAGER:SetReportInterval(ReportInterval) - -
    -
    - -

    Set the reporting time interval.

    - -

    Parameter

    -
      -
    • - -

      #number ReportInterval : -The interval in seconds when a report needs to be done.

      - -
    • -
    -

    Return value

    - -

    #DETECTION_MANAGER: -self

    - -
    -
    -
    -
    - DETECTION_MANAGER:Start() @@ -695,8 +706,8 @@ self

    - -DETECTION_MANAGER._ReportDisplayTime + +DETECTION_MANAGER._RefreshTimeInterval
    @@ -709,8 +720,8 @@ self

    - -DETECTION_MANAGER._ReportInterval + +DETECTION_MANAGER._ReportDisplayTime
    diff --git a/docs/Documentation/Escort.html b/docs/Documentation/Escort.html index ebf59fcc1..82be3f758 100644 --- a/docs/Documentation/Escort.html +++ b/docs/Documentation/Escort.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Event.html b/docs/Documentation/Event.html index 0f212be3f..a7aa2559b 100644 --- a/docs/Documentation/Event.html +++ b/docs/Documentation/Event.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -339,12 +350,6 @@ Example code snippet:

    - - - - @@ -384,7 +389,7 @@ Example code snippet:

    - + @@ -417,18 +422,18 @@ Example code snippet:

    - - - - + + + + @@ -1033,19 +1038,6 @@ The Cargo created.

    - - -
    -
    - - -EVENT.EventsDead - -
    -
    - - -
    @@ -1263,7 +1255,7 @@ The self instance of the class for which the event is.

    -EVENT:OnEventForGroup(GroupName, EventFunction, EventClass, EventID) +EVENT:OnEventForGroup(GroupName, EventFunction, EventClass, EventID, ...)
    @@ -1294,6 +1286,11 @@ The self instance of the class for which the event is.

    EventID :

    + +
  • + +

    ... :

    +
  • Return value

    @@ -1498,8 +1495,29 @@ The self instance of the class for which the event is captured. When the event h
    - -EVENT:Remove(EventClass, EventID) + +EVENT:RemoveAll(EventObject) + +
    +
    + +

    Clears all event subscriptions for a Base#BASE derived object.

    + +

    Parameter

    + +
    +
    +
    +
    + + +EVENT:RemoveEvent(EventClass, EventID)
    @@ -1530,27 +1548,6 @@ The self instance of the class for which the event is.

    - -EVENT:RemoveAll(EventObject) - -
    -
    - -

    Clears all event subscriptions for a Base#BASE derived object.

    - -

    Parameter

    - -
    -
    -
    -
    - EVENT:Reset(EventClass, EventID, EventObject) diff --git a/docs/Documentation/Fsm.html b/docs/Documentation/Fsm.html index e0276d335..de2949f53 100644 --- a/docs/Documentation/Fsm.html +++ b/docs/Documentation/Fsm.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -1598,7 +1609,7 @@ A string defining the start state.

    - + #string FSM._StartState @@ -1897,7 +1908,6 @@ A string defining the start state.

    - FSM.current diff --git a/docs/Documentation/Goal.html b/docs/Documentation/Goal.html new file mode 100644 index 000000000..2ce5c04d5 --- /dev/null +++ b/docs/Documentation/Goal.html @@ -0,0 +1,589 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Goal

    + +

    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)

    + +
    + + +

    Global(s)

    +
    DESIGNATE:ActivateAutoLase()DESIGNATE:AddMenuLaserCode(LaserCode, MenuText) -

    Coordinates the Auto Lase.

    +

    Add a specific lase code to the designate lase menu to lase targets with a specific laser code.

    DESIGNATE.CC +
    DESIGNATE:CoordinateLase() +

    Coordinates the Auto Lase.

    +
    DESIGNATE.DesignateName +
    DESIGNATE.Designating +
    DESIGNATE:DesignationScope() +

    Adapt the designation scope according the detected items.

    DESIGNATE:LaseOn()

    LaseOn Trigger for DESIGNATE

    +
    DESIGNATE.LaseStart +
    DESIGNATE.LaserCodesUsed +
    DESIGNATE.MarkScheduler + +
    DESIGNATE.MaximumDesignations + +
    DESIGNATE.MaximumDistanceAirDesignation + +
    DESIGNATE.MaximumDistanceDesignations + +
    DESIGNATE.MaximumDistanceGroundDesignation + +
    DESIGNATE.MaximumMarkings +
    DESIGNATE:MenuAutoLase(AutoLase) +
    DESIGNATE.MenuDesignate +
    DESIGNATE:MenuFlashStatus(AttackGroup, Flash) +
    DESIGNATE:MenuForget(Index) +
    DESIGNATE:MenuIlluminate(Index) +
    DESIGNATE:MenuLaseCode(Index, Duration, LaserCode) +
    DESIGNATE:MenuLaseOn(Index, Duration) +
    DESIGNATE.MenuLaserCodes +
    DESIGNATE:New(CC, Detection, AttackSet)DESIGNATE.Mission + +
    DESIGNATE:New(CC, Detection, AttackSet, Mission)

    DESIGNATE Constructor.

    DESIGNATE.Recces +
    DESIGNATE:RemoveMenuLaserCode(LaserCode) +

    Removes a specific lase code from the designate lase menu.

    DESIGNATE:SetAutoLase(AutoLase)DESIGNATE:SetAutoLase(AutoLase, Message)

    Set auto lase.

    DESIGNATE:SetDesignateMenu()

    Sets the Designate Menu.

    +
    DESIGNATE:SetDesignateName(DesignateName) +

    Set the name of the designation.

    DESIGNATE:SetLaserCodes(<, LaserCodes)

    Set an array of possible laser codes.

    +
    DESIGNATE:SetMaximumDesignations(MaximumDesignations) +

    Set the maximum amount of designations.

    +
    DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation) +

    Set the maximum air designation distance.

    +
    DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations) +

    Set the overall maximum distance when designations can be accepted.

    +
    DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation) +

    Set the maximum ground designation distance.

    +
    DESIGNATE:SetMaximumMarkings(MaximumMarkings) +

    Set the maximum amount of markings FACs will do, per designated target group.

    +
    DESIGNATE:SetMission(Mission) +

    Set the MISSION object for which designate will function.

    DESIGNATE:onafterLaseOn(From, Event, To, Index, Duration)DESIGNATE:onafterLaseOn(From, Event, To, Index, Duration, LaserCode)
    DESIGNATE:onafterLasing(From, Event, To, Index, Duration)DESIGNATE:onafterLasing(From, Event, To, Index, Duration, LaserCodeRequested)
    DETECTION_AREAS:CalculateThreatLevelA2G(DetectedItem)DETECTION_AREAS:CalculateIntercept(DetectedItem) -

    Calculate the maxium A2G threat level of the DetectedItem.

    +

    Calculate the optimal intercept point of the DetectedItem.

    DETECTION_AREAS.ClassName +
    DETECTION_AREAS.CountryID +
    DETECTION_AREAS:DetectedItemReportSummary(Index, AttackGroup)DETECTION_AREAS:DetectedItemReportSummary(Index, AttackGroup, Settings)

    Report summary of a detected item using a given numeric index.

    DETECTION_AREAS:GetChangeText(DetectedItem)

    Make text documenting the changes of the detected zone.

    -
    DETECTION_AREAS:GetTreatLevelA2G(DetectedItem) -

    Returns the A2G threat level of the units in the DetectedItem

    DETECTION_BASE:CleanDetectionItem(DetectedItem, DetectedItemID) -
    DETECTION_BASE.CountryID -
    DETECTION_BASE:DetectedItemReportSummary(Index, AttackGroup)DETECTION_BASE:DetectedItemReportSummary(Index, AttackGroup, Settings)

    Report summary of a detected item using a given numeric index.

    DETECTION_BASE.DetectionCount -
    DETECTION_BASE.DetectionInterval -
    DETECTION_BASE:FilterFriendliesCategory(FriendliesCategory) +

    Filters friendly units by unit category.

    DETECTION_BASE:ForgetDetectedUnit(UnitName)

    Forget a Unit from a DetectionItem

    +
    DETECTION_BASE.FriendliesCategory +
    DETECTION_BASE.FriendliesRange +
    DETECTION_BASE.FriendlyPrefixes +
    DETECTION_BASE:GetDetectedItemCoordinate(Index) -

    Get the COORDINATE of a detection item using a given numeric index.

    +

    Get the detected item coordinate.

    DETECTION_BASE:GetDetectedItemID(Index)

    Get a detected ItemID using a given numeric index.

    +
    DETECTION_BASE:GetDetectedItemThreatLevel(Index) +

    Get the detected item coordinate.

    DETECTION_BASE:GetDetectedSet(Index)

    Get the Set#SET_UNIT of a detecttion area using a given numeric index.

    +
    DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit) +

    Gets a detected unit type name, taking into account the detection results.

    DETECTION_BASE:GetFriendliesDistance(DetectedItem) -

    Returns friendly units nearby the FAC units sorted per distance ...

    +

    Returns the distance used to identify friendlies near the deteted item ...

    DETECTION_BASE:GetFriendliesNearBy(DetectedItem)

    Returns friendly units nearby the FAC units ...

    +
    DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem) +

    Returns friendly units nearby the intercept point ...

    DETECTION_BASE:InitDetectVisual(DetectVisual)

    Detect Visual.

    +
    DETECTION_BASE.Intercept + +
    DETECTION_BASE.InterceptDelay +
    DETECTION_BASE:IsFriendliesNearBy(DetectedItem)

    Returns if there are friendlies nearby the FAC units ...

    +
    DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem) +

    Returns if there are friendlies nearby the intercept ...

    DETECTION_BASE:OnLeaveStopped(From, Event, To)

    OnLeave Transition Handler for State Stopped.

    +
    DETECTION_BASE.RefreshTimeInterval +
    DETECTION_BASE:SetDetectionInterval(DetectionInterval)DETECTION_BASE:SetDetectedItemCoordinate(The, Coordinate, DetectedItemUnit, DetectedItem) -

    Set the detection interval time in seconds.

    +

    Set the detected item coordinate.

    +
    DETECTION_BASE:SetDetectedItemThreatLevel(The, DetectedItem) +

    Set the detected item threatlevel.

    DETECTION_BASE:SetFriendliesRange(FriendliesRange)

    Set the radius in meters to validate if friendlies are nearby.

    +
    DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes) +

    This will allow during friendly search any recce or detection unit to be also considered as a friendly.

    +
    DETECTION_BASE:SetIntercept(Intercept, IntereptDelay, InterceptDelay) +

    Set the parameters to calculate to optimal intercept point.

    +
    DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval) +

    Set the detection interval time in seconds.

    DETECTION_BASE.DetectedItem.Changes

    A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes).

    +
    DETECTION_BASE.DetectedItem.Coordinate +

    The last known coordinate of the DetectedItem.

    +
    DETECTION_BASE.DetectedItem.DistanceRecce +
    DETECTION_BASE.DetectedItem.ItemIDDETECTION_BASE.DetectedItem.ID

    -- The identifier of the detected area.

    DETECTION_BASE.DetectedItem.MaxThreatLevelA2GDETECTION_BASE.DetectedItem.InterceptCoord
    DETECTION_TYPES:DetectedItemReportSummary(Index, DetectedTypeName, AttackGroup)DETECTION_TYPES:DetectedItemReportSummary(Index, AttackGroup, Settings, DetectedTypeName)

    Report summary of a DetectedItem using a given numeric index.

    DETECTION_UNITS:DetectedItemReportSummary(Index, AttackGroup)DETECTION_UNITS:DetectedItemReportSummary(Index, AttackGroup, Settings)

    Report summary of a DetectedItem using a given numeric index.

    DETECTION_MANAGER:SetRefreshTimeInterval(RefreshTimeInterval) -

    Set the reporting message display time.

    +

    Set the reporting time interval.

    DETECTION_MANAGER:SetReportInterval(ReportInterval)DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime) -

    Set the reporting time interval.

    +

    Set the reporting message display time.

    DETECTION_MANAGER._RefreshTimeInterval
    DETECTION_MANAGER._ReportIntervalDETECTION_MANAGER._ReportDisplayTime EVENT.Events -
    EVENT.EventsDead -
    EVENT:OnEventForGroup(GroupName, EventFunction, EventClass, EventID)EVENT:OnEventForGroup(GroupName, EventFunction, EventClass, EventID, ...)

    Set a new listener for an SEVENTX event for a GROUP.

    EVENT:OnTakeOffForTemplate(EventTemplate, EventFunction, EventClass) -
    EVENT:Remove(EventClass, EventID) -

    Removes a subscription

    EVENT:RemoveAll(EventObject)

    Clears all event subscriptions for a Base#BASE derived object.

    +
    EVENT:RemoveEvent(EventClass, EventID) +

    Removes a subscription

    + + + + +
    GOAL +

    GOAL class, extends Fsm#FSM

    + +

    GOAL models processes that have an objective with a defined achievement.

    +
    +

    Type GOAL

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    GOAL:Achieved() +

    Achieved Trigger for GOAL

    +
    GOAL:AddPlayerContribution(PlayerName) + +
    GOAL:GetPlayerContribution(Player, PlayerName) + +
    GOAL:GetPlayerContributions() + +
    GOAL:GetTotalContributions() + +
    GOAL:IsAchieved() + +
    GOAL:New() +

    GOAL Constructor.

    +
    GOAL:OnAfterAchieved(From, Event, To) +

    Achieved Handler OnAfter for GOAL

    +
    GOAL:OnBeforeAchieved(From, Event, To) +

    Achieved Handler OnBefore for GOAL

    +
    GOAL:OnEnterAchieved(From, Event, To) +

    Achieved State Handler OnEnter for GOAL

    +
    GOAL:OnLeaveAchieved(From, Event, To) +

    Achieved State Handler OnLeave for GOAL

    +
    GOAL.Players + +
    GOAL.TotalContributions + +
    GOAL:__Achieved(Delay) +

    Achieved Asynchronous Trigger for GOAL

    +
    + +

    Global(s)

    +
    +
    + + #GOAL + +GOAL + +
    +
    + +

    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.
    • +
    + + +
    +
    +

    Type Goal

    + +

    Type GOAL

    +

    Field(s)

    +
    +
    + + +GOAL:Achieved() + +
    +
    + +

    Achieved Trigger for GOAL

    + +
    +
    +
    +
    + + +GOAL:AddPlayerContribution(PlayerName) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      #string PlayerName :

      + +
    • +
    +
    +
    +
    +
    + + +GOAL:GetPlayerContribution(Player, PlayerName) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      #number Player : +contribution.

      + +
    • +
    • + +

      PlayerName :

      + +
    • +
    +
    +
    +
    +
    + + +GOAL:GetPlayerContributions() + +
    +
    + + + +
    +
    +
    +
    + + +GOAL:GetTotalContributions() + +
    +
    + + + +
    +
    +
    +
    + + +GOAL:IsAchieved() + +
    +
    + + + +

    Return value

    + +

    #boolean: +true if the goal is Achieved

    + +
    +
    +
    +
    + + +GOAL:New() + +
    +
    + +

    GOAL Constructor.

    + +

    Return value

    + +

    #GOAL:

    + + +
    +
    +
    +
    + + +GOAL:OnAfterAchieved(From, Event, To) + +
    +
    + +

    Achieved Handler OnAfter for GOAL

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +GOAL:OnBeforeAchieved(From, Event, To) + +
    +
    + +

    Achieved Handler OnBefore for GOAL

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +GOAL:OnEnterAchieved(From, Event, To) + +
    +
    + +

    Achieved State Handler OnEnter for GOAL

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +GOAL:OnLeaveAchieved(From, Event, To) + +
    +
    + +

    Achieved State Handler OnLeave for GOAL

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + + +GOAL.Players + +
    +
    + + + +
    +
    +
    +
    + + +GOAL.TotalContributions + +
    +
    + + + +
    +
    +
    +
    + + +GOAL:__Achieved(Delay) + +
    +
    + +

    Achieved Asynchronous Trigger for GOAL

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    + + + + + + diff --git a/docs/Documentation/Group.html b/docs/Documentation/Group.html index d82748101..2e87093a8 100644 --- a/docs/Documentation/Group.html +++ b/docs/Documentation/Group.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -256,6 +267,12 @@
    GROUP:GetDCSUnits()

    Returns the DCS Units of the DCS Group.

    +
    GROUP:GetFuel() +

    Returns relative amount of fuel (from 0.0 to 1.0) the group has in its internal tanks.

    GROUP:GetTemplate()

    Returns the group template from the DATABASE (_DATABASE object).

    +
    GROUP:GetTemplateRoutePoints() +

    Returns the group template route.points[] (the waypoints) from the DATABASE (_DATABASE object).

    GROUP:HandleEvent(Event, EventFunction)GROUP:HandleEvent(Event, EventFunction, ...)

    Subscribe to a DCS Event.

    GROUP:IsShip()

    Returns if the DCS Group contains Ships.

    +
    GROUP:NewTemplate(GroupTemplate, CoalitionSide, CategoryID, CountryID) +

    Create a new GROUP from a given GroupTemplate as a parameter.

    GROUP:Register(GroupName) -

    Create a new GROUP from a DCSGroup

    +

    Create a new GROUP from an existing Group in the Mission.

    IDENTIFIABLE:GetCoalition()

    Returns coalition of the Identifiable.

    +
    IDENTIFIABLE:GetCoalitionName() +

    Returns the name of the coalition of the Identifiable.

    MENU_BASE.MenuRemoveParent +
    MENU_BASE.MenuTag +
    MENU_BASE:SetRemoveParent(RemoveParent)

    Sets a Menu to remove automatically the parent menu when the menu removed is the last child menu of that parent Menu.

    +
    MENU_BASE:SetTag(MenuTag) +

    Sets a tag for later selection of menu refresh.

    MENU_COMMAND_BASE.New(#, self, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments)

    Constructor

    +
    MENU_COMMAND_BASE.SetCommandMenuArguments(#, self, CommandMenuArguments) +

    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!!!

    +
    MENU_COMMAND_BASE.SetCommandMenuFunction(#, self, CommandMenuFunction) +

    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!!!

    @@ -406,13 +448,13 @@ classes are derived from, in order to set commands.

    - MENU_GROUP:Remove(MenuTime) + MENU_GROUP:Remove(MenuTime, MenuTag)

    Removes the main menu and sub menus recursively of this MENU_GROUP.

    - MENU_GROUP:RemoveSubMenus(MenuTime) + MENU_GROUP:RemoveSubMenus(MenuTime, MenuTag)

    Removes the sub menus recursively of this MENU_GROUP.

    @@ -464,7 +506,7 @@ classes are derived from, in order to set commands.

    - MENU_GROUP_COMMAND:Remove(MenuTime) + MENU_GROUP_COMMAND:Remove(MenuTime, MenuTag)

    Removes a menu structure for a group.

    @@ -717,6 +759,7 @@ Using this object reference, you can then remove ALL the menus and submenus unde

    The MENUCOMMANDBASE class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands.

    +
    @@ -896,6 +939,20 @@ The text of the child menu.

    + +
    +
    +
    + + + +MENU_BASE.MenuTag + +
    +
    + + +
    @@ -979,6 +1036,33 @@ If true, the parent menu is automatically removed when this menu is the last chi

    #MENU_BASE:

    + +
    +
    +
    + + +MENU_BASE:SetTag(MenuTag) + +
    +
    + +

    Sets a tag for later selection of menu refresh.

    + +

    Parameter

    +
      +
    • + +

      #string MenuTag : +A Tag or Key that will filter only menu items set with this key.

      + +
    • +
    +

    Return value

    + +

    #MENU_BASE:

    + +
    @@ -1399,6 +1483,86 @@ ENUCOMMANDBASE

    #MENUCOMMANDBASE:

    + +
    +
    +
    + + +MENU_COMMAND_BASE.SetCommandMenuArguments(#, self, CommandMenuArguments) + +
    +
    + +

    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!!!

    + +

    Parameters

    +
      +
    • + +

      # : +ENUCOMMANDBASE

      + +
    • +
    • + +

      self :

      + +
    • +
    • + +

      CommandMenuArguments :

      + +
    • +
    +

    Return value

    + +

    #MENUCOMMANDBASE:

    + + +
    +
    +
    +
    + + +MENU_COMMAND_BASE.SetCommandMenuFunction(#, self, CommandMenuFunction) + +
    +
    + +

    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!!!

    + +

    Parameters

    +
      +
    • + +

      # : +ENUCOMMANDBASE

      + +
    • +
    • + +

      self :

      + +
    • +
    • + +

      CommandMenuFunction :

      + +
    • +
    +

    Return value

    + +

    #MENUCOMMANDBASE:

    + +
    @@ -1534,19 +1698,25 @@ self

    -MENU_GROUP:Remove(MenuTime) +MENU_GROUP:Remove(MenuTime, MenuTag)

    Removes the main menu and sub menus recursively of this MENU_GROUP.

    -

    Parameter

    +

    Parameters

    • MenuTime :

      +
    • +
    • + +

      MenuTag : +A Tag or Key to filter the menus to be refreshed with the Tag set.

      +

    Return value

    @@ -1560,19 +1730,25 @@ self

    -MENU_GROUP:RemoveSubMenus(MenuTime) +MENU_GROUP:RemoveSubMenus(MenuTime, MenuTag)

    Removes the sub menus recursively of this MENU_GROUP.

    -

    Parameter

    +

    Parameters

    • MenuTime :

      +
    • +
    • + +

      MenuTag : +A Tag or Key to filter the menus to be refreshed with the Tag set.

      +

    Return value

    @@ -1723,25 +1899,34 @@ An argument for the function.

    + +

    self:E({Path=Path})

    +
    -MENU_GROUP_COMMAND:Remove(MenuTime) +MENU_GROUP_COMMAND:Remove(MenuTime, MenuTag)

    Removes a menu structure for a group.

    -

    Parameter

    +

    Parameters

    • MenuTime :

      +
    • +
    • + +

      MenuTag : +A Tag or Key to filter the menus to be refreshed with the Tag set.

      +

    Return value

    diff --git a/docs/Documentation/Message.html b/docs/Documentation/Message.html index d4364cb07..6077791d6 100644 --- a/docs/Documentation/Message.html +++ b/docs/Documentation/Message.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -128,9 +139,27 @@

    Type MESSAGE

    + + + + + + + + + + + + @@ -152,13 +181,13 @@ - + - + @@ -170,7 +199,7 @@ - + @@ -179,6 +208,46 @@ + + + + + +
    MESSAGE.MessageCategory + +
    MESSAGE.MessageDuration + +
    MESSAGE:New(MessageText, MessageDuration, MessageCategory)

    Creates a new MESSAGE object.

    +
    MESSAGE:NewType(MessageText, MessageType) +

    Creates a new MESSAGE object of a certain type.

    MESSAGE:ToClient(Client)MESSAGE:ToClient(Client, Settings)

    Sends a MESSAGE to a Client Group.

    MESSAGE:ToCoalition(CoalitionSide)MESSAGE:ToCoalition(CoalitionSide, Settings)

    Sends a MESSAGE to a Coalition.

    MESSAGE:ToGroup(Group)MESSAGE:ToGroup(Group, Settings)

    Sends a MESSAGE to a Group.

    MESSAGE:ToRed()

    Sends a MESSAGE to the Red Coalition.

    +
    MESSAGE.Type + +
    + +

    Type MESSAGE.Type

    + + + + + + + + + + + + + + + + + + + +
    MESSAGE.Type.Briefing + +
    MESSAGE.Type.Detailed + +
    MESSAGE.Type.Information + +
    MESSAGE.Type.Overview + +
    MESSAGE.Type.Update +
    @@ -249,6 +318,37 @@ To send messages, you need to use the To functions.

    + #string + +MESSAGE.MessageCategory + +
    +
    + + + + +

    self.MessageType .. ": "

    + +
    +
    +
    +
    + + + +MESSAGE.MessageDuration + +
    +
    + + + +
    +
    +
    +
    + MESSAGE:New(MessageText, MessageDuration, MessageCategory) @@ -302,6 +402,50 @@ MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25
    + +MESSAGE:NewType(MessageText, MessageType) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #string MessageText : +is the text of the Message.

      + +
    • +
    • + +

      #MESSAGE.Type MessageType : +The type of the message.

      + +
    • +
    +

    Return value

    + +

    #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 )
    + +
    +
    +
    +
    + MESSAGE:ToAll() @@ -383,7 +527,7 @@ MessageBLUE:ToBlue()
    -MESSAGE:ToClient(Client) +MESSAGE:ToClient(Client, Settings)
    @@ -393,13 +537,18 @@ MessageBLUE:ToBlue()

    Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".

    -

    Parameter

    +

    Parameters

    Return value

    @@ -429,20 +578,25 @@ MessageClient2:ToClient( ClientGroup )
    -MESSAGE:ToCoalition(CoalitionSide) +MESSAGE:ToCoalition(CoalitionSide, Settings)

    Sends a MESSAGE to a Coalition.

    -

    Parameter

    +

    Parameters

    • CoalitionSide : needs to be filled out by the defined structure of the standard scripting engine coalition.side.

      +
    • +
    • + +

      Settings :

      +

    Return value

    @@ -497,20 +651,25 @@ needs to be filled out by the defined structure of the standard scripting engine
    -MESSAGE:ToGroup(Group) +MESSAGE:ToGroup(Group, Settings)

    Sends a MESSAGE to a Group.

    -

    Parameter

    +

    Parameters

    Return value

    @@ -545,6 +704,96 @@ 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:ToRed() +
    + +
    +
    + + #MESSAGE.Type + +MESSAGE.Type + +
    +
    + + + +
    +
    + +

    Type MESSAGE.Type

    + +

    Message Types

    + +

    Field(s)

    +
    +
    + + #string + +MESSAGE.Type.Briefing + +
    +
    + + + +
    +
    +
    +
    + + #string + +MESSAGE.Type.Detailed + +
    +
    + + + +
    +
    +
    +
    + + #string + +MESSAGE.Type.Information + +
    +
    + + + +
    +
    +
    +
    + + #string + +MESSAGE.Type.Overview + +
    +
    + + + +
    +
    +
    +
    + + #string + +MESSAGE.Type.Update + +
    +
    + + +
    diff --git a/docs/Documentation/MissileTrainer.html b/docs/Documentation/MissileTrainer.html index 17e2909ef..eb2909293 100644 --- a/docs/Documentation/MissileTrainer.html +++ b/docs/Documentation/MissileTrainer.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Mission.html b/docs/Documentation/Mission.html index 84ce45974..626416cef 100644 --- a/docs/Documentation/Mission.html +++ b/docs/Documentation/Mission.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -133,6 +144,12 @@ MISSION:AbortUnit(PlayerUnit)

    Aborts a PlayerUnit from the Mission.

    + + + + MISSION:AddPlayerName(PlayerName) + + @@ -198,7 +215,7 @@ MISSION:GetMenu(TaskGroup) -

    Gets the mission menu for the coalition.

    +

    Gets the mission menu for the TaskGroup.

    @@ -211,6 +228,18 @@ MISSION:GetNextTaskID(Task)

    Return the next Task ID to be completed within the Mission.

    + + + + MISSION:GetPlayerNames() + + + + + + MISSION:GetRootMenu(TaskGroup) + +

    Gets the root mission menu for the TaskGroup.

    @@ -294,13 +323,19 @@ MISSION:MenuReportBriefing(ReportGroup) - +

    Reports the briefing.

    MISSION:MenuReportPlayersPerTask(ReportGroup) + + + + MISSION:MenuReportPlayersProgress(ReportGroup) + + @@ -312,7 +347,7 @@ MISSION:MenuReportTasksSummary(ReportGroup) - +

    Report the task summary.

    @@ -325,6 +360,12 @@ MISSION:MissionGoals()

    MissionGoals Trigger for MISSION

    + + + + MISSION.MissionGroupMenu + + @@ -499,6 +540,12 @@ MISSION:ReportPlayersPerTask(ReportGroup)

    Create an active player report of the Mission.

    + + + + MISSION:ReportPlayersProgress(ReportGroup) + +

    Create an Mission Progress report of the Mission.

    @@ -508,7 +555,7 @@ - MISSION:ReportSummary() + MISSION:ReportSummary(ReportGroup)

    Create a summary report of the Mission (one line).

    @@ -582,7 +629,7 @@ MISSION:onenterCOMPLETED(From, Event, To) - +

    FSM function for a MISSION

    @@ -644,6 +691,27 @@ The CLIENT or UNIT of the Player joining the Mission.

    + +MISSION:AddPlayerName(PlayerName) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      PlayerName :

      + +
    • +
    +
    +
    +
    +
    + MISSION:AddScoring(Scoring) @@ -855,7 +923,7 @@ The CLIENT or UNIT of the Player crashing.

    -

    Gets the mission menu for the coalition.

    +

    Gets the mission menu for the TaskGroup.

    Parameter

    +
    +
    +
    + + +MISSION:ReportPlayersProgress(ReportGroup) + +
    +
    + +

    Create an Mission Progress report of the Mission.

    + + +

    This reports provides a one liner per player of the mission achievements per task.

    + +
    Mission "<MissionName>" - <MissionStatus> - Active Players Report
    + - Player <PlayerName>: Task <TaskName> <TaskStatus>: <Progress>
    + - Player <PlayerName>: Task <TaskName> <TaskStatus>: <Progress>
    + - ..
    +
    + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +

    Return value

    + +

    #string:

    + +
    @@ -2322,13 +2500,21 @@ self

    -MISSION:ReportSummary() +MISSION:ReportSummary(ReportGroup)

    Create a summary report of the Mission (one line).

    +

    Parameter

    +

    Return value

    #string:

    @@ -2555,30 +2741,23 @@ The delay in seconds.

    - - - -

    FSM function for a MISSION - @param #MISSION self - @param #string From - @param #string Event - @param #string To

    +

    FSM function for a MISSION

    Parameters

    • -

      From :

      +

      #string From :

    • -

      Event :

      +

      #string Event :

    • -

      To :

      +

      #string To :

    diff --git a/docs/Documentation/Movement.html b/docs/Documentation/Movement.html index be5ce073c..32b0acb34 100644 --- a/docs/Documentation/Movement.html +++ b/docs/Documentation/Movement.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -227,7 +238,6 @@ on defined intervals (currently every minute).

    - #number MOVEMENT.AliveUnits @@ -236,9 +246,6 @@ on defined intervals (currently every minute).

    - -

    Contains the counter how many units are currently alive

    -
    diff --git a/docs/Documentation/Object.html b/docs/Documentation/Object.html index e70b8ecd1..24b2ac418 100644 --- a/docs/Documentation/Object.html +++ b/docs/Documentation/Object.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Point.html b/docs/Documentation/Point.html index f204d56d9..6caec0549 100644 --- a/docs/Documentation/Point.html +++ b/docs/Documentation/Point.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -267,6 +278,30 @@ COORDINATE:GetDistanceText(Distance, Settings)

    Provides a distance text expressed in the units of measurement.

    + + + + COORDINATE:GetHeading() + +

    Get the heading of the coordinate, if applicable.

    + + + + COORDINATE:GetHeadingText(Settings) + +

    Return the heading text of the COORDINATE.

    + + + + COORDINATE:GetLandHeight() + +

    Return the height of the land at the coordinate.

    + + + + COORDINATE:GetMovingText(Settings) + +

    Return velocity text of the COORDINATE.

    @@ -297,18 +332,78 @@ COORDINATE:GetVec3()

    Return the coordinates of the COORDINATE in Vec3 format.

    + + + + COORDINATE:GetVelocity() + +

    Return the velocity of the COORDINATE.

    + + + + COORDINATE:GetVelocityText(Settings) + +

    Return the velocity text of the COORDINATE.

    + + + + COORDINATE.Heading + + COORDINATE:IlluminationBomb()

    Creates an illumination bomb at the point.

    + + + + COORDINATE:IsInRadius(ToCoordinate, Radius, Coordinate) + +

    Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis.

    + + + + COORDINATE:IsInSphere(ToCoordinate, Radius, Coordinate) + +

    Returns if a Coordinate is in a certain radius of this Coordinate in 3D space using the X, Y and Z axis.

    COORDINATE:IsLOS(ToCoordinate)

    Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.

    + + + + COORDINATE:MarkToAll(MarkText) + +

    Mark to All

    + + + + COORDINATE:MarkToCoalition(MarkText, Coalition) + +

    Mark to Coalition

    + + + + COORDINATE:MarkToCoalitionBlue(MarkText) + +

    Mark to Blue Coalition

    + + + + COORDINATE:MarkToCoalitionRed(MarkText) + +

    Mark to Red Coalition

    + + + + COORDINATE:MarkToGroup(MarkText, MarkGroup) + +

    Mark to Group

    @@ -330,21 +425,21 @@ - COORDINATE:RoutePointAir(AltType, Type, Action, Speed, SpeedLocked) + COORDINATE:RemoveMark(MarkID) -

    Build an air type route point.

    - - - - COORDINATE:RoutePointGround(Speed, Formation) - -

    Build an ground type route point.

    +

    Remove a mark

    COORDINATE:SetHeading(Heading) - +

    Set the heading of the coordinate, if applicable.

    + + + + COORDINATE:SetVelocity(Velocity) + +

    Set the velocity of the COORDINATE.

    @@ -384,10 +479,22 @@ - COORDINATE:ToString(Controllable, Settings) + COORDINATE:ToString(Controllable, Settings, Task)

    Provides a coordinate string of the point, based on a coordinate format system: * Uses default settings in COORDINATE.

    + + + + COORDINATE:ToStringA2A(Controllable, Settings) + +

    Provides a coordinate string of the point, based on the A2A coordinate format system.

    + + + + COORDINATE:ToStringA2G(Controllable, Settings) + +

    Provides a coordinate string of the point, based on the A2G coordinate format system.

    @@ -422,9 +529,15 @@ - COORDINATE:ToStringLL(Settings) + COORDINATE:ToStringLLDDM(Settings) -

    Provides a Lat Lon string

    +

    Provides a Lat Lon string in Degree Decimal Minute format.

    + + + + COORDINATE:ToStringLLDMS(Settings) + +

    Provides a Lat Lon string in Degree Minute Second format.

    @@ -437,6 +550,78 @@ COORDINATE:Translate(Distance, Angle)

    Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.

    + + + + COORDINATE.Velocity + + + + + + COORDINATE.WaypointAction + + + + + + COORDINATE:WaypointAir(AltType, Type, Action, Speed, SpeedLocked) + +

    Build an air type route point.

    + + + + COORDINATE:WaypointAirFlyOverPoint(AltType, Speed) + +

    Build a Waypoint Air "Fly Over Point".

    + + + + COORDINATE:WaypointAirLanding(Speed) + +

    Build a Waypoint Air "Landing".

    + + + + COORDINATE:WaypointAirTakeOffParking(AltType, Speed) + +

    Build a Waypoint Air "Take Off Parking".

    + + + + COORDINATE:WaypointAirTakeOffParkingHot(AltType, Speed) + +

    Build a Waypoint Air "Take Off Parking Hot".

    + + + + COORDINATE:WaypointAirTakeOffRunway(AltType, Speed) + +

    Build a Waypoint Air "Take Off Runway".

    + + + + COORDINATE:WaypointAirTurningPoint(AltType, Speed) + +

    Build a Waypoint Air "Turning Point".

    + + + + COORDINATE.WaypointAltType + + + + + + COORDINATE:WaypointGround(Speed, Formation) + +

    Build an ground type route point.

    + + + + COORDINATE.WaypointType + + @@ -793,8 +978,8 @@

    A COORDINATE can prepare waypoints for Ground and Air groups to be embedded into a Route.

    Route points can be used in the Route methods of the Group#GROUP class.

    @@ -838,6 +1023,20 @@ +

    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:

    + + + +

    3D calculation methods

    Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:

    @@ -1498,6 +1697,94 @@ The distance in meters.

    #string: The distance text expressed in the units of measurement.

    + +
    +
    +
    + + +COORDINATE:GetHeading() + +
    +
    + +

    Get the heading of the coordinate, if applicable.

    + +

    Return value

    + +

    #number: +or nil

    + +
    +
    +
    +
    + + +COORDINATE:GetHeadingText(Settings) + +
    +
    + +

    Return the heading text of the COORDINATE.

    + +

    Parameter

    +
      +
    • + +

      Settings :

      + +
    • +
    +

    Return value

    + +

    #string: +Heading text.

    + +
    +
    +
    +
    + + +COORDINATE:GetLandHeight() + +
    +
    + +

    Return the height of the land at the coordinate.

    + +

    Return value

    + +

    #number:

    + + +
    +
    +
    +
    + + +COORDINATE:GetMovingText(Settings) + +
    +
    + +

    Return velocity text of the COORDINATE.

    + +

    Parameter

    +
      +
    • + +

      Settings :

      + +
    • +
    +

    Return value

    + +

    #string:

    + +
    @@ -1614,6 +1901,64 @@ The Vec2 format coordinate.

    Dcs.DCSTypes#Vec3: The Vec3 format coordinate.

    + +
    +
    +
    + + +COORDINATE:GetVelocity() + +
    +
    + +

    Return the velocity of the COORDINATE.

    + +

    Return value

    + +

    #number: +Velocity in meters per second.

    + +
    +
    +
    +
    + + +COORDINATE:GetVelocityText(Settings) + +
    +
    + +

    Return the velocity text of the COORDINATE.

    + +

    Parameter

    +
      +
    • + +

      Settings :

      + +
    • +
    +

    Return value

    + +

    #string: +Velocity text.

    + +
    +
    +
    +
    + + + +COORDINATE.Heading + +
    +
    + + +
    @@ -1632,6 +1977,85 @@ The Vec3 format coordinate.

    + +COORDINATE:IsInRadius(ToCoordinate, Radius, Coordinate) + +
    +
    + +

    Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis.

    + +

    Parameters

    +
      +
    • + +

      #COORDINATE ToCoordinate : +The coordinate that will be tested if it is in the radius of this coordinate.

      + +
    • +
    • + +

      #number Radius : +The radius of the circle on the 2D plane around this coordinate.

      + +
    • +
    • + +

      Coordinate :

      + +
    • +
    +

    Return value

    + +

    #boolean: +true if in the Radius.

    + +
    +
    +
    +
    + + +COORDINATE:IsInSphere(ToCoordinate, Radius, Coordinate) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #COORDINATE ToCoordinate : +The coordinate that will be tested if it is in the radius of this coordinate.

      + +
    • +
    • + +

      #number Radius : +The radius of the sphere in the 3D space around this coordinate.

      + +
    • +
    • + +

      Coordinate :

      + +
    • +
    +

    Return value

    + +

    #boolean: +true if in the Sphere.

    + +
    +
    +
    +
    + COORDINATE:IsLOS(ToCoordinate) @@ -1658,6 +2082,173 @@ true If the ToCoordinate has LOS with the Coordinate, otherwise false.

    + +COORDINATE:MarkToAll(MarkText) + +
    +
    + +

    Mark to All

    + +

    Parameter

    +
      +
    • + +

      #string MarkText : +Free format text that shows the marking clarification.

      + +
    • +
    +

    Return value

    + +

    #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" )
    + +
    +
    +
    +
    + + +COORDINATE:MarkToCoalition(MarkText, Coalition) + +
    +
    + +

    Mark to Coalition

    + +

    Parameters

    +
      +
    • + +

      #string MarkText : +Free format text that shows the marking clarification.

      + +
    • +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #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 )
    + +
    +
    +
    +
    + + +COORDINATE:MarkToCoalitionBlue(MarkText) + +
    +
    + +

    Mark to Blue Coalition

    + +

    Parameter

    +
      +
    • + +

      #string MarkText : +Free format text that shows the marking clarification.

      + +
    • +
    +

    Return value

    + +

    #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" )
    + +
    +
    +
    +
    + + +COORDINATE:MarkToCoalitionRed(MarkText) + +
    +
    + +

    Mark to Red Coalition

    + +

    Parameter

    +
      +
    • + +

      #string MarkText : +Free format text that shows the marking clarification.

      + +
    • +
    +

    Return value

    + +

    #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" )
    + +
    +
    +
    +
    + + +COORDINATE:MarkToGroup(MarkText, MarkGroup) + +
    +
    + +

    Mark to Group

    + +

    Parameters

    +
      +
    • + +

      #string MarkText : +Free format text that shows the marking clarification.

      + +
    • +
    • + +

      Wrapper.Group#GROUP MarkGroup : +The Group that receives the mark.

      + +
    • +
    +

    Return value

    + +

    #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 )
    + +
    +
    +
    +
    + COORDINATE:New(x, y, z) @@ -1757,84 +2348,29 @@ The Vec3 point.

    - -COORDINATE:RoutePointAir(AltType, Type, Action, Speed, SpeedLocked) + +COORDINATE:RemoveMark(MarkID)
    -

    Build an air type route point.

    +

    Remove a mark

    -

    Parameters

    +

    Parameter

    -

    Return value

    - -

    #table: -The route point.

    - -
    -
    -
    -
    - - -COORDINATE:RoutePointGround(Speed, Formation) - -
    -
    - -

    Build an ground type route point.

    - -

    Parameters

    - -

    Return value

    - -

    #table: -The route point.

    +

    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
    @@ -1847,7 +2383,7 @@ The route point.

    - +

    Set the heading of the coordinate, if applicable.

    Parameter

    +

    Return value

    + +

    #string: +The coordinate Text in the configured coordinate system.

    + +
    +
    +
    +
    + + +COORDINATE:ToStringA2A(Controllable, Settings) + +
    +
    + +

    Provides a coordinate string of the point, based on the A2A coordinate format system.

    + +

    Parameters

    + +

    Return value

    + +

    #string: +The coordinate Text in the configured coordinate system.

    + +
    +
    +
    +
    + + +COORDINATE:ToStringA2G(Controllable, Settings) + +
    +
    + +

    Provides a coordinate string of the point, based on the A2G coordinate format system.

    + +

    Parameters

    +
    +
    +
    +
    + + + +COORDINATE.Velocity + +
    +
    + + + +
    +
    +
    +
    + + + +COORDINATE.WaypointAction + +
    +
    + + + +
    +
    +
    +
    + + +COORDINATE:WaypointAir(AltType, Type, Action, Speed, SpeedLocked) + +
    +
    + +

    Build an air type route point.

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirFlyOverPoint(AltType, Speed) + +
    +
    + +

    Build a Waypoint Air "Fly Over Point".

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirLanding(Speed) + +
    +
    + +

    Build a Waypoint Air "Landing".

    + +

    Parameter

    + +

    Return value

    + +

    #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.
    +
    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirTakeOffParking(AltType, Speed) + +
    +
    + +

    Build a Waypoint Air "Take Off Parking".

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirTakeOffParkingHot(AltType, Speed) + +
    +
    + +

    Build a Waypoint Air "Take Off Parking Hot".

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirTakeOffRunway(AltType, Speed) + +
    +
    + +

    Build a Waypoint Air "Take Off Runway".

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + +COORDINATE:WaypointAirTurningPoint(AltType, Speed) + +
    +
    + +

    Build a Waypoint Air "Turning Point".

    + +

    Parameters

    + +

    Return value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + + +COORDINATE.WaypointAltType + +
    +
    + + + +
    +
    +
    +
    + + +COORDINATE:WaypointGround(Speed, Formation) + +
    +
    + +

    Build an ground type route point.

    + +

    Parameters

    +
      +
    • + +

      #number Speed : +(optional) Speed in km/h. The default speed is 999 km/h.

      + +
    • +
    • + +

      #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 value

    + +

    #table: +The route point.

    + +
    +
    +
    +
    + + + +COORDINATE.WaypointType + +
    +
    + + +
    @@ -2292,11 +3285,11 @@ The new calculated COORDINATE.

    -

    Type COORDINATE.RoutePointAction

    +

    Type COORDINATE.WaypointAction

    -

    Type COORDINATE.RoutePointAltType

    +

    Type COORDINATE.WaypointAltType

    -

    Type COORDINATE.RoutePointType

    +

    Type COORDINATE.WaypointType

    Type POINT_VEC2

    Field(s)

    diff --git a/docs/Documentation/Positionable.html b/docs/Documentation/Positionable.html index b38e91bd3..7b0f35730 100644 --- a/docs/Documentation/Positionable.html +++ b/docs/Documentation/Positionable.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -146,6 +157,54 @@

    Type POSITIONABLE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -250,7 +315,7 @@ @@ -263,6 +328,18 @@ + + + + + + + + @@ -298,7 +375,7 @@ @@ -347,6 +424,18 @@ + + + + + + + + @@ -356,15 +445,67 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    POSITIONABLE:AddCargo(Cargo) +

    Add cargo.

    +
    POSITIONABLE:CargoItemCount() +

    Get cargo item count.

    +
    POSITIONABLE:ClearCargo() +

    Clear all cargo.

    +
    POSITIONABLE:Flare(FlareColor) +

    Signal a flare at the position of the POSITIONABLE.

    +
    POSITIONABLE:FlareGreen() +

    Signal a green flare at the position of the POSITIONABLE.

    +
    POSITIONABLE:FlareRed() +

    Signal a red flare at the position of the POSITIONABLE.

    +
    POSITIONABLE:FlareWhite() +

    Signal a white flare at the position of the POSITIONABLE.

    +
    POSITIONABLE:FlareYellow() +

    Signal a yellow flare at the position of the POSITIONABLE.

    +
    POSITIONABLE:GetAltitude()

    Returns the altitude of the POSITIONABLE.

    @@ -197,6 +256,12 @@
    POSITIONABLE:GetMessageText(Message, Name)

    Returns the message text with the callsign embedded (if there is one).

    +
    POSITIONABLE:GetMessageType(Message, MessageType, Name) +

    Returns a message of a specified type with the callsign embedded (if there is one).

    POSITIONABLE:GetVelocity() -

    Returns the POSITIONABLE velocity vector.

    +

    Returns the a Velocity object from the positionable.

    POSITIONABLE:GetVelocityMPS()

    Returns the POSITIONABLE velocity in meters per second.

    +
    POSITIONABLE:GetVelocityVec3() +

    Returns the POSITIONABLE velocity Vec3 vector.

    +
    POSITIONABLE:HasCargo(Cargo) +

    Returns if carrier has given cargo.

    POSITIONABLE.LaserCode -

    The last assigned laser code.

    +
    POSITIONABLE:MessageToSetGroup(Message, Duration, MessageSetGroup, Name)

    Send a message to a Set#SET_GROUP.

    +
    POSITIONABLE:MessageTypeToCoalition(Message, MessageType, MessageCoalition) +

    Send a message to a coalition.

    +
    POSITIONABLE:MessageTypeToGroup(Message, MessageType, MessageGroup, Name) +

    Send a message of a message type to a Group.

    POSITIONABLE.PositionableNamePOSITIONABLE:RemoveCargo(Cargo) -

    The name of the measurable.

    +

    Remove cargo.

    +
    POSITIONABLE:Smoke(SmokeColor, Range, AddHeight) +

    Smoke the POSITIONABLE.

    +
    POSITIONABLE:SmokeBlue() +

    Smoke the POSITIONABLE Blue.

    +
    POSITIONABLE:SmokeGreen() +

    Smoke the POSITIONABLE Green.

    +
    POSITIONABLE:SmokeOrange() +

    Smoke the POSITIONABLE Orange.

    +
    POSITIONABLE:SmokeRed() +

    Smoke the POSITIONABLE Red.

    +
    POSITIONABLE:SmokeWhite() +

    Smoke the POSITIONABLE White.

    POSITIONABLE.Spot -

    The laser Spot.

    + +
    POSITIONABLE.__ + +
    + +

    Type POSITIONABLE.__

    + + + +
    POSITIONABLE.__.Cargo +
    @@ -438,10 +579,137 @@ The method POSITIONABLE.GetVelocity()

    Type POSITIONABLE

    - -

    The POSITIONABLE class

    +

    Field(s)

    +
    +
    -

    Field(s)

    + +POSITIONABLE:AddCargo(Cargo) + +
    +
    + +

    Add cargo.

    + +

    Parameter

    + +

    Return value

    + +

    #POSITIONABLE:

    + + +
    +
    +
    +
    + + +POSITIONABLE:CargoItemCount() + +
    +
    + +

    Get cargo item count.

    + +

    Return value

    + +

    Core.Cargo#CARGO: +Cargo

    + +
    +
    +
    +
    + + +POSITIONABLE:ClearCargo() + +
    +
    + +

    Clear all cargo.

    + +
    +
    +
    +
    + + +POSITIONABLE:Flare(FlareColor) + +
    +
    + +

    Signal a flare at the position of the POSITIONABLE.

    + +

    Parameter

    + +
    +
    +
    +
    + + +POSITIONABLE:FlareGreen() + +
    +
    + +

    Signal a green flare at the position of the POSITIONABLE.

    + +
    +
    +
    +
    + + +POSITIONABLE:FlareRed() + +
    +
    + +

    Signal a red flare at the position of the POSITIONABLE.

    + +
    +
    +
    +
    + + +POSITIONABLE:FlareWhite() + +
    +
    + +

    Signal a white flare at the position of the POSITIONABLE.

    + +
    +
    +
    +
    + + +POSITIONABLE:FlareYellow() + +
    +
    + +

    Signal a yellow flare at the position of the POSITIONABLE.

    + +
    +
    @@ -527,21 +795,11 @@ The POSITIONABLE is not existing or alive.

    Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission.

    -

    Return values

    -
      -
    1. +

      Return value

      Core.Point#COORDINATE: The COORDINATE of the POSITIONABLE.

      -
    2. -
    3. - -

      #nil: -The POSITIONABLE is not existing or alive.

      - -
    4. -
    @@ -688,6 +946,45 @@ The message text

    #string: The message text

    + +
    +
    +
    + + +POSITIONABLE:GetMessageType(Message, MessageType, Name) + +
    +
    + +

    Returns a message of a specified type with the callsign embedded (if there is one).

    + +

    Parameters

    +
      +
    • + +

      #string Message : +The message text

      + +
    • +
    • + +

      Core.Message#MESSAGE MessageType : +MessageType The message type.

      + +
    • +
    • + +

      #string Name : +(optional) The Name of the sender. If not provided, the Name is the type of the Positionable.

      + +
    • +
    +

    Return value

    + +

    Core.Message#MESSAGE:

    + +
    @@ -918,14 +1215,14 @@ The POSITIONABLE is not existing or alive.

    -

    Returns the POSITIONABLE velocity vector.

    +

    Returns the a Velocity object from the positionable.

    Return values

    1. -

      Dcs.DCSTypes#Vec3: -The velocity vector

      +

      Core.Velocity#VELOCITY: +Velocity The Velocity object.

    2. @@ -948,12 +1245,48 @@ The POSITIONABLE is not existing or alive.

      Returns the POSITIONABLE velocity in km/h.

      +

      Return value

      + +

      #number: +The velocity in km/h

      + +
    +
    +
    +
    + + +POSITIONABLE:GetVelocityMPS() + +
    +
    + +

    Returns the POSITIONABLE velocity in meters per second.

    + +

    Return value

    + +

    #number: +The velocity in meters per second.

    + +
    +
    +
    +
    + + +POSITIONABLE:GetVelocityVec3() + +
    +
    + +

    Returns the POSITIONABLE velocity Vec3 vector.

    +

    Return values

    1. -

      #number: -The velocity in km/h

      +

      Dcs.DCSTypes#Vec3: +The velocity Vec3 vector

    2. @@ -968,29 +1301,27 @@ The POSITIONABLE is not existing or alive.

      - -POSITIONABLE:GetVelocityMPS() + +POSITIONABLE:HasCargo(Cargo)
      -

      Returns the POSITIONABLE velocity in meters per second.

      +

      Returns if carrier has given cargo.

      -

      Return values

      -
        +

        Parameter

        +
        • -

          #number: -The velocity in meters per second.

          +

          Cargo :

        • -
        • +
        +

        Return value

        -

        #nil: -The POSITIONABLE is not existing or alive.

        +

        Core.Cargo#CARGO: +Cargo

        - -
      @@ -1127,14 +1458,14 @@ true if it is lasing a target

      - #number + POSITIONABLE.LaserCode
      -

      The last assigned laser code.

      +
      @@ -1455,6 +1786,86 @@ The SET_GROUP collection receiving the message.

      + +POSITIONABLE:MessageTypeToCoalition(Message, MessageType, MessageCoalition) + +
      +
      + +

      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.

      + +

      Parameters

      + +
      +
      +
      +
      + + +POSITIONABLE:MessageTypeToGroup(Message, MessageType, MessageGroup, Name) + +
      +
      + +

      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.

      + +

      Parameters

      +
        +
      • + +

        #string Message : +The message text

        + +
      • +
      • + +

        Core.Message#MESSAGE.Type MessageType : +The message type that determines the duration.

        + +
      • +
      • + +

        Wrapper.Group#GROUP MessageGroup : +The GROUP object receiving the message.

        + +
      • +
      • + +

        #string Name : +(optional) The Name of the sender. If not provided, the Name is the type of the Positionable.

        + +
      • +
      +
      +
      +
      +
      + POSITIONABLE:New(PositionableName) @@ -1482,14 +1893,125 @@ self

      - #string - -POSITIONABLE.PositionableName + +POSITIONABLE:RemoveCargo(Cargo)
      -

      The name of the measurable.

      +

      Remove cargo.

      + +

      Parameter

      + +

      Return value

      + +

      #POSITIONABLE:

      + + +
      +
      +
      +
      + + +POSITIONABLE:Smoke(SmokeColor, Range, AddHeight) + +
      +
      + +

      Smoke the POSITIONABLE.

      + +

      Parameters

      +
        +
      • + +

        Utilities.Utils#SMOKECOLOR SmokeColor : +The color to smoke to positionable.

        + +
      • +
      • + +

        #number Range : +The range in meters to randomize the smoking around the positionable.

        + +
      • +
      • + +

        #number AddHeight : +The height in meters to add to the altitude of the positionable.

        + +
      • +
      +
      +
      +
      +
      + + +POSITIONABLE:SmokeBlue() + +
      +
      + +

      Smoke the POSITIONABLE Blue.

      + +
      +
      +
      +
      + + +POSITIONABLE:SmokeGreen() + +
      +
      + +

      Smoke the POSITIONABLE Green.

      + +
      +
      +
      +
      + + +POSITIONABLE:SmokeOrange() + +
      +
      + +

      Smoke the POSITIONABLE Orange.

      + +
      +
      +
      +
      + + +POSITIONABLE:SmokeRed() + +
      +
      + +

      Smoke the POSITIONABLE Red.

      + +
      +
      +
      +
      + + +POSITIONABLE:SmokeWhite() + +
      +
      + +

      Smoke the POSITIONABLE White.

      @@ -1503,11 +2025,44 @@ self

      -

      The laser Spot.

      + + +
      +
      +
      +
      + + #POSITIONABLE.__ + +POSITIONABLE.__ + +
      +
      + +
      +

      Type POSITIONABLE.__

      +

      Field(s)

      +
      +
      + + #POSITIONABLE.__.Cargo + +POSITIONABLE.__.Cargo + +
      +
      + + + +
      +
      + +

      Type POSITIONABLE.__.Cargo

      +

      Type RADIO

      diff --git a/docs/Documentation/Process_JTAC.html b/docs/Documentation/Process_JTAC.html index 666b88494..35e9a46df 100644 --- a/docs/Documentation/Process_JTAC.html +++ b/docs/Documentation/Process_JTAC.html @@ -28,9 +28,9 @@
    3. AI_Cas
    4. AI_Formation
    5. AI_Patrol
    6. +
    7. ATC_Ground
    8. Account
    9. Airbase
    10. -
    11. AirbasePolice
    12. Assign
    13. Base
    14. Cargo
    15. @@ -60,6 +60,7 @@
    16. Escort
    17. Event
    18. Fsm
    19. +
    20. Goal
    21. Group
    22. Identifiable
    23. Menu
    24. @@ -72,7 +73,9 @@
    25. Positionable
    26. Process_JTAC
    27. Process_Pickup
    28. +
    29. Protect
    30. Radio
    31. +
    32. Rat
    33. Route
    34. Scenery
    35. ScheduleDispatcher
    36. @@ -88,6 +91,7 @@
    37. Static
    38. StaticObject
    39. Task
    40. +
    41. TaskZoneCapture
    42. Task_A2A
    43. Task_A2A_Dispatcher
    44. Task_A2G
    45. @@ -95,8 +99,15 @@
    46. Task_Cargo
    47. Task_PICKUP
    48. Unit
    49. +
    50. UserFlag
    51. +
    52. UserSound
    53. Utils
    54. +
    55. Velocity
    56. Zone
    57. +
    58. ZoneCaptureCoalition
    59. +
    60. ZoneGoal
    61. +
    62. ZoneGoalCargo
    63. +
    64. ZoneGoalCoalition
    65. env
    66. land
    67. routines
    68. diff --git a/docs/Documentation/Process_Pickup.html b/docs/Documentation/Process_Pickup.html index a74b64401..719b1ee9c 100644 --- a/docs/Documentation/Process_Pickup.html +++ b/docs/Documentation/Process_Pickup.html @@ -28,9 +28,9 @@
    69. AI_Cas
    70. AI_Formation
    71. AI_Patrol
    72. +
    73. ATC_Ground
    74. Account
    75. Airbase
    76. -
    77. AirbasePolice
    78. Assign
    79. Base
    80. Cargo
    81. @@ -60,6 +60,7 @@
    82. Escort
    83. Event
    84. Fsm
    85. +
    86. Goal
    87. Group
    88. Identifiable
    89. Menu
    90. @@ -72,7 +73,9 @@
    91. Positionable
    92. Process_JTAC
    93. Process_Pickup
    94. +
    95. Protect
    96. Radio
    97. +
    98. Rat
    99. Route
    100. Scenery
    101. ScheduleDispatcher
    102. @@ -88,6 +91,7 @@
    103. Static
    104. StaticObject
    105. Task
    106. +
    107. TaskZoneCapture
    108. Task_A2A
    109. Task_A2A_Dispatcher
    110. Task_A2G
    111. @@ -95,8 +99,15 @@
    112. Task_Cargo
    113. Task_PICKUP
    114. Unit
    115. +
    116. UserFlag
    117. +
    118. UserSound
    119. Utils
    120. +
    121. Velocity
    122. Zone
    123. +
    124. ZoneCaptureCoalition
    125. +
    126. ZoneGoal
    127. +
    128. ZoneGoalCargo
    129. +
    130. ZoneGoalCoalition
    131. env
    132. land
    133. routines
    134. diff --git a/docs/Documentation/Protect.html b/docs/Documentation/Protect.html new file mode 100644 index 000000000..de7a57685 --- /dev/null +++ b/docs/Documentation/Protect.html @@ -0,0 +1,758 @@ + + + + + + +
      +
      + +
      +
      +
      +
      + +
      +

      Module Protect

      + +

      Functional -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.

      + + + +
      + +

      Author: Sven Van de Velde (FlightControl)

      +

      Contributions: MillerTime

      + +
      + + +

      Global(s)

      + + + + + +
      PROTECT +

      PROTECT, extends Base#BASE

      + +
      +

      Type PROTECT

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      PROTECT:AreProtectStaticsAlive() +

      Check if the statics are still alive.

      +
      PROTECT:AreProtectUnitsAlive() +

      Check if the units are still alive.

      +
      PROTECT.Coalition + +
      PROTECT:Flare(FlareColor) +

      Flare.

      +
      PROTECT:GetCoalition() +

      Get the owning coalition of the zone.

      +
      PROTECT:GetCoalitionName() +

      Get the owning coalition name of the zone.

      +
      PROTECT:GetProtectZone() +

      Get the ProtectZone

      +
      PROTECT:GetProtectZoneName() +

      Get the name of the ProtectZone

      +
      PROTECT:IsAttacked() + +
      PROTECT:IsCaptureUnitInZone() +

      Check if there is a capture unit in the zone.

      +
      PROTECT:IsCaptured() + +
      PROTECT:IsEmpty() + +
      PROTECT:IsGuarded() + +
      PROTECT:Mark() +

      Mark.

      +
      PROTECT.MarkBlue + +
      PROTECT.MarkRed + +
      PROTECT:SetCoalition(Coalition) +

      Set the owning coalition of the zone.

      +
      PROTECT:Smoke(SmokeColor) +

      Smoke.

      +
      PROTECT.SmokeColor + +
      PROTECT.SmokeTime + +
      PROTECT:StatusCoalition() +

      Check status Coalition ownership.

      +
      PROTECT:StatusSmoke() +

      Check status Smoke.

      +
      PROTECT:StatusZone() +

      Check status Zone.

      +
      PROTECT:onafterStart() +

      Bound.

      +
      PROTECT:onenterAttacked() + +
      PROTECT:onenterCaptured() + +
      PROTECT:onenterEmpty() + +
      PROTECT:onenterGuarded() +

      Bound.

      +
      + +

      Global(s)

      +
      +
      + + #PROTECT + +PROTECT + +
      +
      + +

      PROTECT, extends Base#BASE

      + + +
      +
      +

      Type Protect

      + +

      Type PROTECT

      +

      Field(s)

      +
      +
      + + +PROTECT:AreProtectStaticsAlive() + +
      +
      + +

      Check if the statics are still alive.

      + +
      +
      +
      +
      + + +PROTECT:AreProtectUnitsAlive() + +
      +
      + +

      Check if the units are still alive.

      + +
      +
      +
      +
      + + + +PROTECT.Coalition + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:Flare(FlareColor) + +
      +
      + +

      Flare.

      + +

      Parameter

      + +
      +
      +
      +
      + + +PROTECT:GetCoalition() + +
      +
      + +

      Get the owning coalition of the zone.

      + +

      Return value

      + +

      DCSCoalition.DCSCoalition#coalition: +Coalition.

      + +
      +
      +
      +
      + + +PROTECT:GetCoalitionName() + +
      +
      + +

      Get the owning coalition name of the zone.

      + +

      Return value

      + +

      #string: +Coalition name.

      + +
      +
      +
      +
      + + +PROTECT:GetProtectZone() + +
      +
      + +

      Get the ProtectZone

      + +

      Return value

      + +

      Core.Zone#ZONE_BASE:

      + + +
      +
      +
      +
      + + +PROTECT:GetProtectZoneName() + +
      +
      + +

      Get the name of the ProtectZone

      + +

      Return value

      + +

      #string:

      + + +
      +
      +
      +
      + + +PROTECT:IsAttacked() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:IsCaptureUnitInZone() + +
      +
      + +

      Check if there is a capture unit in the zone.

      + +
      +
      +
      +
      + + +PROTECT:IsCaptured() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:IsEmpty() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:IsGuarded() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:Mark() + +
      +
      + +

      Mark.

      + +
      +
      +
      +
      + + + +PROTECT.MarkBlue + +
      +
      + + + +
      +
      +
      +
      + + + +PROTECT.MarkRed + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:SetCoalition(Coalition) + +
      +
      + +

      Set the owning coalition of the zone.

      + +

      Parameter

      + +
      +
      +
      +
      + + +PROTECT:Smoke(SmokeColor) + +
      +
      + +

      Smoke.

      + +

      Parameter

      + +
      +
      +
      +
      + + + +PROTECT.SmokeColor + +
      +
      + + + +
      +
      +
      +
      + + + +PROTECT.SmokeTime + +
      +
      + + + + +

      self.SmokeColor = nil

      + +
      +
      +
      +
      + + +PROTECT:StatusCoalition() + +
      +
      + +

      Check status Coalition ownership.

      + +
      +
      +
      +
      + + +PROTECT:StatusSmoke() + +
      +
      + +

      Check status Smoke.

      + +
      +
      +
      +
      + + +PROTECT:StatusZone() + +
      +
      + +

      Check status Zone.

      + +
      +
      +
      +
      + + +PROTECT:onafterStart() + +
      +
      + +

      Bound.

      + +
      +
      +
      +
      + + +PROTECT:onenterAttacked() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:onenterCaptured() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:onenterEmpty() + +
      +
      + + + +
      +
      +
      +
      + + +PROTECT:onenterGuarded() + +
      +
      + +

      Bound.

      + +
      +
      + +

      Type PROTECT.__

      + +

      Type SMOKECOLOR.Color

      + +
      + +
      + + diff --git a/docs/Documentation/Radio.html b/docs/Documentation/Radio.html index dd150fc26..5db376990 100644 --- a/docs/Documentation/Radio.html +++ b/docs/Documentation/Radio.html @@ -28,9 +28,9 @@
    135. AI_Cas
    136. AI_Formation
    137. AI_Patrol
    138. +
    139. ATC_Ground
    140. Account
    141. Airbase
    142. -
    143. AirbasePolice
    144. Assign
    145. Base
    146. Cargo
    147. @@ -60,6 +60,7 @@
    148. Escort
    149. Event
    150. Fsm
    151. +
    152. Goal
    153. Group
    154. Identifiable
    155. Menu
    156. @@ -72,7 +73,9 @@
    157. Positionable
    158. Process_JTAC
    159. Process_Pickup
    160. +
    161. Protect
    162. Radio
    163. +
    164. Rat
    165. Route
    166. Scenery
    167. ScheduleDispatcher
    168. @@ -88,6 +91,7 @@
    169. Static
    170. StaticObject
    171. Task
    172. +
    173. TaskZoneCapture
    174. Task_A2A
    175. Task_A2A_Dispatcher
    176. Task_A2G
    177. @@ -95,8 +99,15 @@
    178. Task_Cargo
    179. Task_PICKUP
    180. Unit
    181. +
    182. UserFlag
    183. +
    184. UserSound
    185. Utils
    186. +
    187. Velocity
    188. Zone
    189. +
    190. ZoneCaptureCoalition
    191. +
    192. ZoneGoal
    193. +
    194. ZoneGoalCargo
    195. +
    196. ZoneGoalCoalition
    197. env
    198. land
    199. routines
    200. diff --git a/docs/Documentation/Rat.html b/docs/Documentation/Rat.html new file mode 100644 index 000000000..81e84e622 --- /dev/null +++ b/docs/Documentation/Rat.html @@ -0,0 +1,5639 @@ + + + + + + +
      +
      + +
      +
      +
      +
      + +
      +

      Module Rat

      + +
        +
      • Functional - Create random airtraffic in your missions.
      • +
      + + +

      +Banner Image

      + +
      + +

      The aim of the RAT class is to fill the empty DCS world with randomized air traffic and bring more life to your airports.

      + +

      In particular, it is designed to spawn AI air units at random airports. These units will be assigned a random flight path to another random airport on the map.

      + +

      Even the mission designer will not know where aircraft will be spawned and which route they follow.

      + +

      Features

      + +
        +
      • Very simple interface. Just one unit and two lines of Lua code needed to fill your map.
      • +
      • High degree of randomization. Aircraft will spawn at random airports, have random routes and random destinations.
      • +
      • Specific departure and/or destination airports can be chosen.
      • +
      • Departure and destination airports can be restricted by coalition.
      • +
      • Planes and helicopters supported. Helicopters can also be send to FARPs and ships.
      • +
      • Units can also be spawned in air within pre-defined zones of the map.
      • +
      • Aircraft will be removed when they arrive at their destination (or get stuck on the ground).
      • +
      • When a unit is removed a new unit with a different flight plan is respawned.
      • +
      • Aircraft can report their status during the route.
      • +
      • All of the above can be customized by the user if necessary.
      • +
      • All current (Caucasus, Nevada, Normandy) and future maps are supported.
      • +
      + +

      The RAT class creates an entry in the F10 menu which allows to

      + +
        +
      • Create new groups on-the-fly, i.e. at run time within the mission,
      • +
      • Destroy specific groups (e.g. if they get stuck or damaged and block a runway),
      • +
      • Request the status of all RAT aircraft or individual groups,
      • +
      • Place markers at waypoints on the F10 map for each group.
      • +
      + +

      Note that by its very nature, this class is suited best for civil or transport aircraft. However, it also works perfectly fine for military aircraft of any kind.

      + +

      More of the documentation include some simple examples can be found further down this page.

      + +
      + +

      Demo Missions

      + +

      RAT Demo Missions

      +

      ALL Demo Missions pack of the last release

      + +
      + +

      YouTube Channel

      + +

      RAT videos are work in progress.

      +

      MOOSE YouTube Channel

      + +
      + +

      Author: funkyfranky

      + +

      Contributions: Sven van de Velde (FlightControl)

      + +
      + +

      Global(s)

      + + + + + +
      RAT +

      RAT class, extends Spawn#SPAWN

      +

      The RAT class implements an easy to use way to randomly fill your map with AI aircraft.

      +
      +

      Type RAT

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      RAT.ATC + +
      RAT:ATC_Clearance(n) +

      Max number of planes that get landing clearance of the RAT ATC.

      +
      RAT:ATC_Delay(time) +

      Delay between granting landing clearance for simultanious landings.

      +
      RAT.ATCswitch +

      Enable/disable ATC if set to true/false.

      +
      RAT:AddFriendlyAirportsToDepartures() +

      Add all friendly airports to the list of possible departures.

      +
      RAT:AddFriendlyAirportsToDestinations() +

      Add all friendly airports to the list of possible destinations

      +
      RAT.AlphaDescent +

      Default angle of descenti in degrees. A value of 3.6 follows the 3:1 rule of 3 miles of travel and 1000 ft descent.

      +
      RAT:ChangeAircraft(actype) +

      Change aircraft type.

      +
      RAT.ClassName +

      Name of the Class.

      +
      RAT:ClearForLanding(name) +

      Clear flight for landing.

      +
      RAT:Commute() +

      Aircraft will commute between their departure and destination airports or zones.

      +
      RAT:ContinueJourney() +

      Aircraft will continue their journey from their destination.

      +
      RAT:DestinationZone() +

      Destinations are treated as zones.

      +
      RAT:EnableATC(switch) +

      Enable ATC, which manages the landing queue for RAT aircraft if they arrive simultaniously at the same airport.

      +
      RAT:ExcludedAirports(ports) +

      Airports, FARPs and ships explicitly excluded as departures and destinations.

      +
      RAT.FLcruise +

      Cruise altitude of aircraft. Default FL200 for planes and F005 for helos.

      +
      RAT.FLmaxuser +

      Maximum flight level set by user.

      +
      RAT.FLminuser +

      Minimum flight level set by user.

      +
      RAT.FLuser +

      Flight level set by users explicitly.

      +
      RAT:Livery(skins) +

      Set livery of aircraft.

      +
      RAT.Menu +

      F10 menu items for this RAT object.

      +
      RAT.MenuF10 +

      Main F10 menu.

      +
      RAT:MenuName(name) +

      Set the name of the F10 submenu.

      +
      RAT.Ndeparture_Airports +

      Number of departure airports set via SetDeparture().

      +
      RAT.Ndeparture_Zones +

      Number of departure zones set via SetDeparture.

      +
      RAT.Ndestination_Airports +

      Number of destination airports set via SetDestination().

      +
      RAT.Ndestination_Zones +

      Number of destination zones set via SetDestination().

      +
      RAT:New(groupname, alias) +

      Create a new RAT object.

      +
      RAT:NoRespawn() +

      Aircraft will not get respawned when they finished their route.

      +
      RAT:PlaceMarkers(switch) +

      Place markers of waypoints on the F10 map.

      +
      RAT.ROE +

      RAT rules of engagement.

      +
      RAT.ROT +

      RAT reaction to threat.

      +
      RAT:RadioFrequency(frequency) +

      Set radio frequency.

      +
      RAT:RadioModulation(modulation) +

      Set radio modulation.

      +
      RAT:RadioOFF() +

      Disable Radio.

      +
      RAT:RadioON() +

      Enable Radio.

      +
      RAT:RespawnAfterLanding(delay) +

      Make aircraft respawn the moment they land rather than at engine shut down.

      +
      RAT:RespawnAfterTakeoff() +

      Aircraft will be respawned directly after take-off.

      +
      RAT:ReturnZone() +

      Aircraft will fly to a random point within a zone and then return to its departure airport or zone.

      +
      RAT:SetAISkill(skill) +

      Set skill of AI aircraft.

      +
      RAT:SetClimbRate(rate) +

      Set the climb rate.

      +
      RAT:SetCoalition(friendly) +

      Set the friendly coalitions from which the airports can be used as departure and destination.

      +
      RAT:SetCoalitionAircraft(color) +

      Set coalition of RAT group.

      +
      RAT:SetCountry(id) +

      Set country of RAT group.

      +
      RAT:SetCruiseAltitude(alt) +

      Set cruising altitude.

      +
      RAT:SetDeparture(departurenames) +

      Set possible departure ports.

      +
      RAT:SetDeparturesFromZone(zone) +

      Include all airports which lie in a zone as possible destinations.

      +
      RAT:SetDescentAngle(angle) +

      Set the angle of descent.

      +
      RAT:SetDestination(destinationnames) +

      Set name of destination airports or zones for the AI aircraft.

      +
      RAT:SetDestinationsFromZone(zone) +

      Include all airports which lie in a zone as possible destinations.

      +
      RAT:SetFL(FL) +

      Set flight level.

      +
      RAT:SetFLcruise(FL) +

      Set flight level of cruising part.

      +
      RAT:SetFLmax(FL) +

      Set max flight level.

      +
      RAT:SetFLmin(FL) +

      Set min flight level.

      +
      RAT:SetMaxCruiseAltitude(alt) +

      Set max cruising altitude above sea level.

      +
      RAT:SetMaxCruiseSpeed(speed) +

      Set the maximum cruise speed of the aircraft.

      +
      RAT:SetMaxDistance(dist) +

      Set maximum distance between departure and destination.

      +
      RAT:SetMinCruiseAltitude(alt) +

      Set min cruising altitude above sea level.

      +
      RAT:SetMinDistance(dist) +

      Set minimum distance between departure and destination.

      +
      RAT:SetParkingID(id) +

      Set parking id of aircraft.

      +
      RAT:SetROE(roe) +

      Set rules of engagement (ROE).

      +
      RAT:SetROT(rot) +

      Set reaction to threat (ROT).

      +
      RAT:SetSpawnDelay(delay) +

      Set the delay before first group is spawned.

      +
      RAT:SetSpawnInterval(interval) +

      Set the interval between spawnings of the template group.

      +
      RAT:SetTakeoff(type) +

      Set takeoff type.

      +
      RAT:Spawn(naircraft) +

      Triggers the spawning of AI aircraft.

      +
      RAT:Status(message, forID) +

      Report status of RAT groups.

      +
      RAT:StatusReports(switch) +

      Aircraft report status update messages along the route.

      +
      RAT.SubMenuName +

      Submenu name for RAT object.

      +
      RAT:TimeDestroyInactive(time) +

      Set the time after which inactive groups will be destroyed.

      +
      RAT.Tinactive +

      Time in seconds after which inactive units will be destroyed. Default is 300 seconds.

      +
      RAT:Uncontrolled() +

      Spawn aircraft in uncontolled state.

      +
      RAT.Vclimb +

      Default climb rate in ft/min.

      +
      RAT.Vcruisemax +

      Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user.

      +
      RAT:_ATCAddFlight(name, dest) +

      Adds andd initializes a new flight after it was spawned.

      +
      RAT:_ATCCheck() +

      Main ATC function.

      +
      RAT:_ATCClearForLanding(airport, flight) +

      Giving landing clearance for aircraft by setting user flag.

      +
      RAT:_ATCDelFlight(t, entry) +

      Deletes a flight from ATC lists after it landed.

      +
      RAT:_ATCFlightLanded(name) +

      Takes care of organisational stuff after a plane has landed.

      +
      RAT:_ATCInit(airports_map) +

      Initializes the ATC arrays and starts schedulers.

      +
      RAT:_ATCQueue() +

      Creates a landing queue for all flights holding at airports.

      +
      RAT:_ATCRegisterFlight(name, time) +

      Registers a flight once it is near its holding point at the final destination.

      +
      RAT:_ATCStatus() +

      ATC status report about flights.

      +
      RAT:_AddFriendlyAirports(ports) +

      Add names of all friendly airports to possible departure or destination airports if they are not already in the list.

      +
      RAT:_AirportExists(name) +

      Test if an airport exists on the current map.

      +
      RAT:_AnticipatedGroupName(index) +

      Anticipated group name from alias and spawn index.

      +
      RAT:_CheckConsistency() +

      Function checks consistency of user input and automatically adjusts parameters if necessary.

      +
      RAT:_Course(a, b) +

      Determine the heading from point a to point b.

      +
      RAT:_Debug(switch) +

      Turn debug messages on or off.

      +
      RAT:_DeleteMarkers() +

      Delete all markers on F10 map.

      +
      RAT:_Despawn(group) +

      Despawn unit.

      +
      RAT:_EngineStartup(EventData) +

      Function is executed when a unit starts its engines.

      +
      RAT:_Excluded(port) +

      Check if airport is excluded from possible departures and destinations.

      +
      RAT:_FLmax(alpha, beta, d, phi, h0) +

      Calculate the max flight level for a given distance and fixed climb and descent rates.

      +
      RAT:_GetAirportsInZone(zone) +

      Find airports within a zone.

      +
      RAT:_GetAirportsOfCoalition() +

      Get all "friendly" airports of the current map.

      +
      RAT:_GetAirportsOfMap() +

      Get all airports of the current map.

      +
      RAT:_GetLife(group) +

      Get (relative) life of first unit of a group.

      +
      RAT:_Heading(course) +

      Determine the heading for an aircraft to be entered in the route template.

      +
      RAT:_InitAircraft(DCSgroup) +

      Initialize basic parameters of the aircraft based on its (template) group in the mission editor.

      +
      RAT:_IsFriendly(port) +

      Check if airport is friendly, i.e.

      +
      RAT:_MinDistance(alpha, beta, ha, hb) +

      Calculate minimum distance between departure and destination for given minimum flight level and climb/decent rates.

      +
      RAT:_ModifySpawnTemplate(waypoints, livery) +

      Modifies the template of the group to be spawned.

      +
      RAT:_NameInList(liste, name) +

      Check if a name/string is in a list or not.

      +
      RAT:_OnBirth(EventData) +

      Function is executed when a unit is spawned.

      +
      RAT:_OnCrash(EventData) +

      Function is executed when a unit crashes.

      +
      RAT:_OnDead(EventData) +

      Function is executed when a unit is dead.

      +
      RAT:_OnEngineShutdown(EventData) +

      Function is executed when a unit shuts down its engines.

      +
      RAT:_OnLand(EventData) +

      Function is executed when a unit lands.

      +
      RAT:_OnTakeoff(EventData) +

      Function is executed when a unit takes off.

      +
      RAT:_PickDeparture(takeoff) +

      Set the departure airport of the AI.

      +
      RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) +

      Pick destination airport or zone depending on departure position.

      +
      RAT:_PlaceMarkers(waypoints, index) +

      Place markers of the waypoints.

      +
      RAT._Random_Gaussian(x0, sigma, xmin, xmax, self) +

      Generate Gaussian pseudo-random numbers.

      +
      RAT:_Randomize(value, fac, lower, upper) +

      Randomize a value by a certain amount.

      +
      RAT:_Respawn(group) +

      Respawn a group.

      +
      RAT:_Routeinfo(waypoints, comment) +

      Provide information about the assigned flightplan.

      +
      RAT:_SetCoalitionTable() +

      Create a table with the valid coalitions for departure and destination airports.

      +
      RAT:_SetMarker(text, wp, index) +

      Set a marker visible for all on the F10 map.

      +
      RAT:_SetROE(group, roe) +

      Set ROE for a group.

      +
      RAT:_SetROT(group, rot) +

      Set ROT for a group.

      +
      RAT:_SetRoute(Takeoff, Landing, _departure, _destination, takeoff, landing, _waypoint) +

      Set the route of the AI plane.

      +
      RAT:_SetStatus(group, status) +

      Set status of group.

      +
      RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint) +

      Spawn the AI aircraft with a route.

      +
      RAT:_TaskFunction(FunctionString, ...) +

      Task function.

      +
      RAT:_TaskHolding(P1, Altitude, Speed, Duration) +

      Orbit at a specified position at a specified alititude with a specified speed.

      +
      RAT:_Waypoint(Running, Type, Coord, Speed, Altitude, Airport, index) +

      Create a waypoint that can be used with the Route command.

      +
      RAT._WaypointFunction(group, rat, wp) +

      Function which is called after passing every waypoint.

      +
      RAT:_ZoneExists(name) +

      Test if a trigger zone defined in the mission editor exists.

      +
      RAT.actype + +
      RAT.addfriendlydepartures +

      Add all friendly airports to departures.

      +
      RAT.addfriendlydestinations +

      Add all friendly airports to destinations.

      +
      RAT.aircraft +

      Table which holds the basic aircraft properties (speed, range, ...).

      +
      RAT.airports +

      All airports of friedly coalitions.

      +
      RAT.airports_map +

      All airports available on current map (Caucasus, Nevada, Normandy, ...).

      +
      RAT.alias +

      Alias for spawned group.

      +
      RAT.alive +

      Number of groups which are alive.

      +
      RAT.cat + +
      RAT.category +

      Category of aircarft: "plane" or "heli".

      +
      RAT.coal +

      RAT friendly coalitions.

      +
      RAT.coalition +

      Coalition of spawn group template.

      +
      RAT.commute +

      Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.

      +
      RAT.continuejourney +

      Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.

      +
      RAT.country +

      Country of spawn group template.

      +
      RAT.ctable +

      Table with the valid coalitons from choice self.friendly.

      +
      RAT.debug +

      Turn debug messages on or off.

      +
      RAT.departure_Azone +

      Zone containing the departure airports.

      +
      RAT.departure_ports +

      Array containing the names of the destination airports or zones.

      +
      RAT.destination_Azone +

      Zone containing the destination airports.

      +
      RAT.destination_ports +

      Array containing the names of the destination airports or zones.

      +
      RAT.destinationzone +

      Destination is a zone and not an airport.

      +
      RAT.excluded_ports +

      Array containing the names of explicitly excluded airports.

      +
      RAT.f10menu +

      Add an F10 menu for RAT.

      +
      RAT.frequency +

      Radio frequency used by the RAT groups.

      +
      RAT.friendly +

      Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red.

      +
      RAT.id +

      Some ID to identify who we are in output of the DCS.log file.

      +
      RAT.landing +

      Landing type. Determines if we actually land at an airport or treat it as zone.

      +
      RAT.livery +

      Livery of the aircraft set by user.

      +
      RAT.markerid +

      Running number of placed markers on the F10 map.

      +
      RAT.markerids +

      Array with marker IDs.

      +
      RAT.maxdist +

      Max distance from departure to destination in meters. Default 5000 km.

      +
      RAT.mindist +

      Min distance from departure to destination in meters. Default 5 km.

      +
      RAT.modulation +

      Ratio modulation. Either "FM" or "AM".

      +
      RAT.ngroups +

      Number of groups to be spawned in total.

      +
      RAT.norespawn +

      Aircraft will not be respawned after they have finished their route.

      +
      RAT.onboard_num + +
      RAT.parking_id +

      String with a special parking ID for the aircraft.

      +
      RAT.placemarkers +

      Place markers of waypoints on F10 map.

      +
      RAT.radio +

      If true/false disables radio messages from the RAT groups.

      +
      RAT.random_departure +

      By default a random friendly airport is chosen as departure.

      +
      RAT.random_destination +

      By default a random friendly airport is chosen as destination.

      +
      RAT.ratcraft +

      Array with the spawned RAT aircraft.

      +
      RAT.reportstatus +

      Aircraft report status.

      +
      RAT.respawn_after_takeoff +

      Aircraft will be respawned directly after take-off.

      +
      RAT.respawn_at_landing +

      Respawn aircraft the moment they land rather than at engine shutdown.

      +
      RAT.respawn_delay +

      Delay in seconds until repawn happens after landing.

      +
      RAT.return_zones +

      Array containing the names of the return zones.

      +
      RAT.returnzone +

      Zone where aircraft will fly to before returning to their departure airport.

      +
      RAT.roe +

      ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free".

      +
      RAT.rot +

      ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade".

      +
      RAT.skill +

      Skill of AI.

      +
      RAT.spawndelay +

      Delay time in seconds before first spawning happens.

      +
      RAT.spawninitialized +

      If RAT:Spawn() was already called this RAT object is set to true to prevent users to call it again.

      +
      RAT.spawninterval +

      Interval between spawning units/groups. Note that we add a randomization of 50%.

      +
      RAT.status +

      RAT aircraft status.

      +
      RAT.statusinterval +

      Intervall between status checks (and reports if enabled).

      +
      RAT.takeoff +

      Takeoff type. 0=coldorhot.

      +
      RAT.templategroup +

      Group serving as template for the RAT aircraft.

      +
      RAT.uncontrolled +

      If true aircraft are spawned in uncontrolled state and will only sit on their parking spots.

      +
      RAT.unit +

      RAT unit conversions.

      +
      RAT.version +

      RAT version.

      +
      RAT.waypointdescriptions +

      Table with strings for waypoint descriptions of markers.

      +
      RAT.waypointstatus +

      Table with strings of waypoint status.

      +
      RAT.wp +

      RAT waypoint type.

      +
      + +

      Global(s)

      +
      +
      + + #RAT + +RAT + +
      +
      + +

      RAT class, extends Spawn#SPAWN

      +

      The RAT class implements an easy to use way to randomly fill your map with AI aircraft.

      + + + + +

      Airport Selection

      + +

      Process

      + +

      Default settings:

      + +
        +
      • By default, aircraft are spawned at airports of their own coalition (blue or red) or neutral airports.
      • +
      • Destination airports are by default also of neutral or of the same coalition as the template group of the spawned aircraft.
      • +
      • Possible destinations are restricted by their distance to the departure airport. The maximal distance depends on the max range of spawned aircraft type and its initial fuel amount.
      • +
      + +

      The default behavior can be changed:

      + +
        +
      • A specific departure and/or destination airport can be chosen.
      • +
      • Valid coalitions can be set, e.g. only red, blue or neutral, all three "colours".
      • +
      • It is possible to start in air within a zone defined in the mission editor or within a zone above an airport of the map.
      • +
      + +

      Flight Plan

      + +

      Process

      + +
        +
      • A general flight plan has five main airborne segments: Climb, cruise, descent, holding and final approach.
      • +
      • Events monitored during the flight are: birth, engine-start, take-off, landing and engine-shutdown.
      • +
      • The default flight level (FL) is set to ~FL200, i.e. 20000 feet ASL but randomized for each aircraft. + Service ceiling of aircraft type is into account for max FL as well as the distance between departure and destination.
      • +
      • Maximal distance between destination and departure airports depends on range and initial fuel of aircraft.
      • +
      • Climb rate is set to a moderate value of ~1500 ft/min.
      • +
      • The standard descent rate follows the 3:1 rule, i.e. 1000 ft decent per 3 miles of travel. Hence, angle of descent is ~3.6 degrees.
      • +
      • A holding point is randomly selected at a distance between 5 and 10 km away from destination airport.
      • +
      • The altitude of theholding point is ~1200 m AGL. Holding patterns might or might not happen with variable duration.
      • +
      • If an aircraft is spawned in air, the procedure omitts taxi and take-off and starts with the climb/cruising part.
      • +
      • All values are randomized for each spawned aircraft.
      • +
      + +

      Mission Editor Setup

      + +

      Process

      + +

      Basic mission setup is very simple and essentially a three step process:

      + +
        +
      • Place your aircraft anywhere on the map. It really does not matter where you put it.
      • +
      • Give the group a good name. In the example above the group is named "RAT_YAK".
      • +
      • Activate the "LATE ACTIVATION" tick box. Note that this aircraft will not be spawned itself but serves a template for each RAT aircraft spawned when the mission starts.
      • +
      + +

      Voilà, your already done!

      + +

      Optionally, you can set a specific livery for the aircraft or give it some weapons. +However, the aircraft will by default not engage any enemies. Think of them as beeing on a peaceful or ferry mission.

      + +

      Basic Lua Script

      + +

      Process

      + +

      The basic Lua script for one template group consits of two simple lines as shown in the picture above.

      + +
        +
      • Line 2 creates a new RAT object "yak". The only required parameter for the constructor RAT.New() is the name of the group as defined in the mission editor. In this example it is "RAT_YAK".
      • +
      • Line 5 trigger the command to spawn the aircraft. The (optional) parameter for the RAT.Spawn() function is the number of aircraft to be spawned of this object. + By default each of these aircraft gets a random departure airport anywhere on the map and a random destination airport, which lies within range of the of the selected aircraft type.
      • +
      + +

      In this simple example aircraft are respawned with a completely new flightplan when they have reached their destination airport. +The "old" aircraft is despawned (destroyed) after it has shut-down its engines and a new aircraft of the same type is spawned at a random departure airport anywhere on the map. +Hence, the default flight plan for a RAT aircraft will be: Fly from airport A to B, get respawned at C and fly to D, get respawned at E and fly to F, ... +This ensures that you always have a constant number of AI aircraft on your map.

      + +

      Examples

      + +

      Here are a few examples, how you can modify the default settings of RAT class objects.

      + +

      Specify Departure and Destinations

      + +

      Process

      + +

      In the picture above you find a few possibilities how to modify the default behaviour to spawn at random airports and fly to random destinations.

      + +

      In particular, you can specify fixed departure and/or destination airports. This is done via the RAT.SetDeparture() or RAT.SetDestination() functions, respectively.

      + +
        +
      • If you only fix a specific departure airport via RAT.SetDeparture() all aircraft will be spawned at that airport and get random destination airports.
      • +
      • If you only fix the destination airport via RAT.SetDestination(), aircraft a spawned at random departure airports but will all fly to the destination airport.
      • +
      • If you fix departure and destination airports, aircraft will only travel from between those airports. + When the aircraft reaches its destination, it will be respawned at its departure and fly again to its destination.
      • +
      + +

      There is also an option that allows aircraft to "continue their journey" from their destination. This is achieved by the RAT.ContinueJourney() function. +In that case, when the aircraft arrives at its first destination it will be respawned at that very airport and get a new random destination. +So the flight plan in this case would be: Fly from airport A to B, then from B to C, then from C to D, ...

      + +

      It is also possible to make aircraft "commute" between two airports, i.e. flying from airport A to B and then back from B to A, etc. +This can be done by the RAT.Commute() function. Note that if no departure or destination airports are specified, the first departure and destination are chosen randomly. +Then the aircraft will fly back and forth between those two airports indefinetly.

      + + +

      Spawn in Air

      + +

      Process

      + +

      Aircraft can also be spawned in air rather than at airports on the ground. This is done by setting RAT.SetTakeoff() to "air".

      + +

      By default, aircraft are spawned randomly above airports of the map.

      + +

      The RAT.SetDeparture() option can be used to specify zones, which have been defined in the mission editor as departure zones. +Aircraft will then be spawned at a random point within the zone or zones.

      + +

      Note that RAT.SetDeparture() also accepts airport names. For an air takeoff these are treated like zones with a radius of XX kilometers. +Again, aircraft are spawned at random points within these zones around the airport.

      + +

      Misc Options

      + +

      Process

      + +

      The default "takeoff" type of RAT aircraft is that they are spawned with hot or cold engines. +The choice is random, so 50% of aircraft will be spawned with hot engines while the other 50% will be spawned with cold engines. +This setting can be changed using the RAT.SetTakeoff() function. The possible parameters for starting on ground are:

      + +
        +
      • RAT.SetTakeoff("cold"), which means that all aircraft are spawned with their engines off,
      • +
      • RAT.SetTakeoff("hot"), which means that all aircraft are spawned with their engines on,
      • +
      • RAT.SetTakeoff("runway"), which means that all aircraft are spawned already at the runway ready to takeoff. + Note that in this case the default spawn intervall is set to 180 seconds in order to avoid aircraft jamms on the runway. Generally, this takeoff at runways should be used with care and problems are to be expected.
      • +
      + + +

      The options RAT.SetMinDistance() and RAT.SetMaxDistance() can be used to restrict the range from departure to destination. For example

      + +
        +
      • RAT.SetMinDistance(100) will cause only random destination airports to be selected which are at least 100 km away from the departure airport.
      • +
      • RAT.SetMaxDistance(150) will allow only destination airports which are less than 150 km away from the departure airport.
      • +
      + +

      Process

      + +

      By default planes get a cruise altitude of ~20,000 ft ASL. The actual altitude is sampled from a Gaussian distribution. The picture shows this distribution +if one would spawn 1000 planes. As can be seen most planes get a cruising alt of around FL200. Other values are possible but less likely the further away +one gets from the expectation value.

      + +

      The expectation value, i.e. the altitude most aircraft get, can be set with the function RAT.SetFLcruise(). +It is possible to restrict the minimum cruise altitude by RAT.SetFLmin() and the maximum cruise altitude by RAT.SetFLmax()

      + +

      The cruise altitude can also be given in meters ASL by the functions RAT.SetCruiseAltitude(), RAT.SetMinCruiseAltitude() and RAT.SetMaxCruiseAltitude().

      + +

      For example:

      + +
        +
      • RAT.SetFLcruise(300) will cause most planes fly around FL300.
      • +
      • RAT.SetFLmin(100) restricts the cruising alt such that no plane will fly below FL100. Note that this automatically changes the minimum distance from departure to destination. + That means that only destinations are possible for which the aircraft has had enought time to reach that flight level and descent again.
      • +
      • RAT.SetFLmax(200) will restrict the cruise alt to maximum FL200, i.e. no aircraft will travel above this height.
      • +
      + + + +
      +
      +

      Type Rat

      + +

      Type RAT

      + +
        +
      • RAT class
      • +
      + +

      Field(s)

      +
      +
      + + + +RAT.ATC + +
      +
      + + + +
      +
      +
      +
      + + +RAT:ATC_Clearance(n) + +
      +
      + +

      Max number of planes that get landing clearance of the RAT ATC.

      + + +

      This setting effects all RAT objects and groups!

      + +

      Parameter

      +
        +
      • + +

        #number n : +Number of aircraft that are allowed to land simultaniously. Default is 2.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:ATC_Delay(time) + +
      +
      + +

      Delay between granting landing clearance for simultanious landings.

      + + +

      This setting effects all RAT objects and groups!

      + +

      Parameter

      +
        +
      • + +

        #number time : +Delay time when the next aircraft will get landing clearance event if the previous one did not land yet. Default is 240 sec.

        + +
      • +
      +
      +
      +
      +
      + + #boolean + +RAT.ATCswitch + +
      +
      + +

      Enable/disable ATC if set to true/false.

      + +
      +
      +
      +
      + + +RAT:AddFriendlyAirportsToDepartures() + +
      +
      + +

      Add all friendly airports to the list of possible departures.

      + +
      +
      +
      +
      + + +RAT:AddFriendlyAirportsToDestinations() + +
      +
      + +

      Add all friendly airports to the list of possible destinations

      + +
      +
      +
      +
      + + #number + +RAT.AlphaDescent + +
      +
      + +

      Default angle of descenti in degrees. A value of 3.6 follows the 3:1 rule of 3 miles of travel and 1000 ft descent.

      + +
      +
      +
      +
      + + +RAT:ChangeAircraft(actype) + +
      +
      + +

      Change aircraft type.

      + + +

      This is a dirty hack which allows to change the aircraft type of the template group. +Note that all parameters like cruise speed, climb rate, range etc are still taken from the template group which likely leads to strange behaviour.

      + +

      Parameter

      +
        +
      • + +

        #string actype : +Type of aircraft which is spawned independent of the template group. Use with care and expect problems!

        + +
      • +
      +
      +
      +
      +
      + + #string + +RAT.ClassName + +
      +
      + +

      Name of the Class.

      + +
      +
      +
      +
      + + +RAT:ClearForLanding(name) + +
      +
      + +

      Clear flight for landing.

      + + +

      Sets tigger value to 1.

      + +

      Parameter

      +
        +
      • + +

        #string name : +Name of flight to be cleared for landing.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:Commute() + +
      +
      + +

      Aircraft will commute between their departure and destination airports or zones.

      + +
      +
      +
      +
      + + +RAT:ContinueJourney() + +
      +
      + +

      Aircraft will continue their journey from their destination.

      + + +

      This means they are respawned at their destination and get a new random destination.

      + +
      +
      +
      +
      + + +RAT:DestinationZone() + +
      +
      + +

      Destinations are treated as zones.

      + + +

      Aircraft will not land but rather be despawned when they reach a random point in the zone.

      + +
      +
      +
      +
      + + +RAT:EnableATC(switch) + +
      +
      + +

      Enable ATC, which manages the landing queue for RAT aircraft if they arrive simultaniously at the same airport.

      + +

      Parameter

      +
        +
      • + +

        #boolean switch : +Enable ATC (true) or Disable ATC (false). No argument means ATC enabled.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:ExcludedAirports(ports) + +
      +
      + +

      Airports, FARPs and ships explicitly excluded as departures and destinations.

      + +

      Parameter

      +
        +
      • + +

        #string ports : +Name or table of names of excluded airports.

        + +
      • +
      +
      +
      +
      +
      + + #number + +RAT.FLcruise + +
      +
      + +

      Cruise altitude of aircraft. Default FL200 for planes and F005 for helos.

      + +
      +
      +
      +
      + + #number + +RAT.FLmaxuser + +
      +
      + +

      Maximum flight level set by user.

      + +
      +
      +
      +
      + + #number + +RAT.FLminuser + +
      +
      + +

      Minimum flight level set by user.

      + +
      +
      +
      +
      + + #number + +RAT.FLuser + +
      +
      + +

      Flight level set by users explicitly.

      + +
      +
      +
      +
      + + +RAT:Livery(skins) + +
      +
      + +

      Set livery of aircraft.

      + + +

      If more than one livery is specified in a table, the actually used one is chosen randomly from the selection.

      + +

      Parameter

      +
        +
      • + +

        #table skins : +Name of livery or table of names of liveries.

        + +
      • +
      +
      +
      +
      +
      + + #table + +RAT.Menu + +
      +
      + +

      F10 menu items for this RAT object.

      + +
      +
      +
      +
      + + #string + +RAT.MenuF10 + +
      +
      + +

      Main F10 menu.

      + +
      +
      +
      +
      + + +RAT:MenuName(name) + +
      +
      + +

      Set the name of the F10 submenu.

      + + +

      Default is the name of the template group.

      + +

      Parameter

      +
        +
      • + +

        #string name : +Submenu name.

        + +
      • +
      +
      +
      +
      +
      + + #number + +RAT.Ndeparture_Airports + +
      +
      + +

      Number of departure airports set via SetDeparture().

      + +
      +
      +
      +
      + + #number + +RAT.Ndeparture_Zones + +
      +
      + +

      Number of departure zones set via SetDeparture.

      + +
      +
      +
      +
      + + #number + +RAT.Ndestination_Airports + +
      +
      + +

      Number of destination airports set via SetDestination().

      + +
      +
      +
      +
      + + #number + +RAT.Ndestination_Zones + +
      +
      + +

      Number of destination zones set via SetDestination().

      + +
      +
      +
      +
      + + +RAT:New(groupname, alias) + +
      +
      + +

      Create a new RAT object.

      + +

      Parameters

      +
        +
      • + +

        #string groupname : +Name of the group as defined in the mission editor. This group is serving as a template for all spawned units.

        + +
      • +
      • + +

        #string alias : +(Optional) Alias of the group. This is and optional parameter but must(!) be used if the same template group is used for more than one RAT object.

        + +
      • +
      +

      Return values

      +
        +
      1. + +

        #RAT: +Object of RAT class.

        + +
      2. +
      3. + +

        #nil: +If the group does not exist in the mission editor.

        + +
      4. +
      +

      Usages:

      +
        +
      • yak1:RAT("RAT_YAK") will create a RAT object called "yak1". The template group in the mission editor must have the name "RAT_YAK".
      • +
      • yak2:RAT("RAT_YAK", "Yak2") will create a RAT object "yak2". The template group in the mission editor must have the name "RAT_YAK" but the group will be called "Yak2" in e.g. the F10 menu.
      • +
      + +
      +
      +
      +
      + + +RAT:NoRespawn() + +
      +
      + +

      Aircraft will not get respawned when they finished their route.

      + +
      +
      +
      +
      + + +RAT:PlaceMarkers(switch) + +
      +
      + +

      Place markers of waypoints on the F10 map.

      + + +

      Default is off.

      + +

      Parameter

      +
        +
      • + +

        #boolean switch : +true=yes, false=no.

        + +
      • +
      +
      +
      +
      +
      + + + +RAT.ROE + +
      +
      + +

      RAT rules of engagement.

      + +
      +
      +
      +
      + + + +RAT.ROT + +
      +
      + +

      RAT reaction to threat.

      + +
      +
      +
      +
      + + +RAT:RadioFrequency(frequency) + +
      +
      + +

      Set radio frequency.

      + +

      Parameter

      +
        +
      • + +

        #number frequency : +Radio frequency.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:RadioModulation(modulation) + +
      +
      + +

      Set radio modulation.

      + + +

      Default is AM.

      + +

      Parameter

      +
        +
      • + +

        #string modulation : +Either "FM" or "AM". If no value is given, modulation is set to AM.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:RadioOFF() + +
      +
      + +

      Disable Radio.

      + + +

      Overrules the ME setting.

      + +
      +
      +
      +
      + + +RAT:RadioON() + +
      +
      + +

      Enable Radio.

      + + +

      Overrules the ME setting.

      + +
      +
      +
      +
      + + +RAT:RespawnAfterLanding(delay) + +
      +
      + +

      Make aircraft respawn the moment they land rather than at engine shut down.

      + +

      Parameter

      +
        +
      • + +

        #number delay : +(Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. Minimum is 0.5 seconds.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:RespawnAfterTakeoff() + +
      +
      + +

      Aircraft will be respawned directly after take-off.

      + +
      +
      +
      +
      + + +RAT:ReturnZone() + +
      +
      + +

      Aircraft will fly to a random point within a zone and then return to its departure airport or zone.

      + +
      +
      +
      +
      + + +RAT:SetAISkill(skill) + +
      +
      + +

      Set skill of AI aircraft.

      + + +

      Default is "High".

      + +

      Parameter

      +
        +
      • + +

        #string skill : +Skill, options are "Average", "Good", "High", "Excellent" and "Random". Parameter is case insensitive.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetClimbRate(rate) + +
      +
      + +

      Set the climb rate.

      + + +

      This automatically sets the climb angle.

      + +

      Parameter

      +
        +
      • + +

        #number rate : +Climb rate in ft/min. Default is 1500 ft/min. Minimum is 100 ft/min. Maximum is 15,000 ft/min.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetCoalition(friendly) + +
      +
      + +

      Set the friendly coalitions from which the airports can be used as departure and destination.

      + +

      Parameter

      +
        +
      • + +

        #string friendly : +"same"=own coalition+neutral (default), "sameonly"=own coalition only, "neutral"=all neutral airports. +Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.

        + +
      • +
      +

      Usages:

      +
        +
      • yak:SetCoalition("neutral") will spawn aircraft randomly on all neutral airports.
      • +
      • yak:SetCoalition("sameonly") will spawn aircraft randomly on airports belonging to the same coalition only as the template.
      • +
      + +
      +
      +
      +
      + + +RAT:SetCoalitionAircraft(color) + +
      +
      + +

      Set coalition of RAT group.

      + + +

      You can make red templates blue and vice versa.

      + +

      Parameter

      +
        +
      • + +

        #string color : +Color of coalition, i.e. "red" or blue".

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetCountry(id) + +
      +
      + +

      Set country of RAT group.

      + + +

      This overrules the coalition settings.

      + +

      Parameter

      +
        +
      • + +

        #number id : +DCS country enumerator ID. For example country.id.USA or country.id.RUSSIA.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetCruiseAltitude(alt) + +
      +
      + +

      Set cruising altitude.

      + + +

      This is still be checked for consitancy with selected route and prone to radomization.

      + +

      Parameter

      +
        +
      • + +

        #number alt : +Cruising altitude ASL in meters.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetDeparture(departurenames) + +
      +
      + +

      Set possible departure ports.

      + + +

      This can be an airport or a zone defined in the mission editor.

      + +

      Parameter

      +
        +
      • + +

        #string departurenames : +Name or table of names of departure airports or zones.

        + +
      • +
      +

      Usages:

      +
        +
      • RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport.
      • +
      • RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport.
      • +
      • RAT:SetDeparture({"Zone A", "Gudauta"}) will spawn RAT aircraft in air randomly within Zone A, which has to be defined in the mission editor, or within a zone around Gudauta airport. Note that this also requires RAT:takeoff("air") to be set.
      • +
      + +
      +
      +
      +
      + + +RAT:SetDeparturesFromZone(zone) + +
      +
      + +

      Include all airports which lie in a zone as possible destinations.

      + +

      Parameter

      +
        +
      • + +

        Core.Zone#ZONE zone : +Zone in which the destination airports lie. Has to be a MOOSE zone.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetDescentAngle(angle) + +
      +
      + +

      Set the angle of descent.

      + + +

      Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel.

      + +

      Parameter

      +
        +
      • + +

        #number angle : +Angle of descent in degrees. Minimum is 0.5 deg. Maximum 50 deg.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetDestination(destinationnames) + +
      +
      + +

      Set name of destination airports or zones for the AI aircraft.

      + +

      Parameter

      +
        +
      • + +

        #string destinationnames : +Name of the destination airport or table of destination airports.

        + +
      • +
      +

      Usage:

      +
      RAT:SetDestination("Krymsk") makes all aircraft of this RAT oject fly to Krymsk airport.
      + +
      +
      +
      +
      + + +RAT:SetDestinationsFromZone(zone) + +
      +
      + +

      Include all airports which lie in a zone as possible destinations.

      + +

      Parameter

      +
        +
      • + +

        Core.Zone#ZONE zone : +Zone in which the departure airports lie. Has to be a MOOSE zone.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetFL(FL) + +
      +
      + +

      Set flight level.

      + + +

      Setting this value will overrule all other logic. Aircraft will try to fly at this height regardless.

      + +

      Parameter

      +
        +
      • + +

        #number FL : +Fight Level in hundrets of feet. E.g. FL200 = 20000 ft ASL.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetFLcruise(FL) + +
      +
      + +

      Set flight level of cruising part.

      + + +

      This is still be checked for consitancy with selected route and prone to radomization. +Default is FL200 for planes and FL005 for helicopters.

      + +

      Parameter

      +
        +
      • + +

        #number FL : +Flight level in hundrets of feet. E.g. FL200 = 20000 ft ASL.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetFLmax(FL) + +
      +
      + +

      Set max flight level.

      + + +

      Setting this value will overrule all other logic. Aircraft will try to fly at less than this FL regardless.

      + +

      Parameter

      +
        +
      • + +

        #number FL : +Maximum Fight Level in hundrets of feet.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetFLmin(FL) + +
      +
      + +

      Set min flight level.

      + + +

      Setting this value will overrule all other logic. Aircraft will try to fly at higher than this FL regardless.

      + +

      Parameter

      +
        +
      • + +

        #number FL : +Maximum Fight Level in hundrets of feet.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetMaxCruiseAltitude(alt) + +
      +
      + +

      Set max cruising altitude above sea level.

      + +

      Parameter

      +
        +
      • + +

        #number alt : +Altitude ASL in meters.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetMaxCruiseSpeed(speed) + +
      +
      + +

      Set the maximum cruise speed of the aircraft.

      + +

      Parameter

      +
        +
      • + +

        #number speed : +Speed in km/h.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetMaxDistance(dist) + +
      +
      + +

      Set maximum distance between departure and destination.

      + + +

      Default is 5000 km but aircarft range is also taken into account automatically.

      + +

      Parameter

      +
        +
      • + +

        #number dist : +Distance in km.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetMinCruiseAltitude(alt) + +
      +
      + +

      Set min cruising altitude above sea level.

      + +

      Parameter

      +
        +
      • + +

        #number alt : +Altitude ASL in meters.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetMinDistance(dist) + +
      +
      + +

      Set minimum distance between departure and destination.

      + + +

      Default is 5 km. +Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different.

      + +

      Parameter

      +
        +
      • + +

        #number dist : +Distance in km.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetParkingID(id) + +
      +
      + +

      Set parking id of aircraft.

      + +

      Parameter

      +
        +
      • + +

        #string id : +Parking ID of the aircraft.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetROE(roe) + +
      +
      + +

      Set rules of engagement (ROE).

      + + +

      Default is weapon hold. This is a peaceful class.

      + +

      Parameter

      +
        +
      • + +

        #string roe : +"hold" = weapon hold, "return" = return fire, "free" = weapons free.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetROT(rot) + +
      +
      + +

      Set reaction to threat (ROT).

      + + +

      Default is no reaction, i.e. aircraft will simply ignore all enemies.

      + +

      Parameter

      +
        +
      • + +

        #string rot : +"noreaction" = no reaction to threats, "passive" = passive defence, "evade" = evade enemy attacks.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetSpawnDelay(delay) + +
      +
      + +

      Set the delay before first group is spawned.

      + +

      Parameter

      +
        +
      • + +

        #number delay : +Delay in seconds. Default is 5 seconds. Minimum delay is 0.5 seconds.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetSpawnInterval(interval) + +
      +
      + +

      Set the interval between spawnings of the template group.

      + +

      Parameter

      +
        +
      • + +

        #number interval : +Interval in seconds. Default is 5 seconds. Minimum is 0.5 seconds.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:SetTakeoff(type) + +
      +
      + +

      Set takeoff type.

      + + +

      Starting cold at airport, starting hot at airport, starting at runway, starting in the air. +Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines.

      + +

      Parameter

      +
        +
      • + +

        #string type : +Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air".

        + +
      • +
      +

      Usages:

      +
        +
      • RAT:Takeoff("hot") will spawn RAT objects at airports with engines started.
      • +
      • RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
      • +
      • RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. 
      • +
      + +
      +
      +
      +
      + + +RAT:Spawn(naircraft) + +
      +
      + +

      Triggers the spawning of AI aircraft.

      + + +

      Note that all additional options should be set before giving the spawn command.

      + +

      Parameter

      +
        +
      • + +

        #number naircraft : +(Optional) Number of aircraft to spawn. Default is one aircraft.

        + +
      • +
      +

      Usage:

      +
      yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
      + +
      +
      +
      +
      + + +RAT:Status(message, forID) + +
      +
      + +

      Report status of RAT groups.

      + +

      Parameters

      +
        +
      • + +

        #boolean message : +(Optional) Send message with report to all if true.

        + +
      • +
      • + +

        #number forID : +(Optional) Send message only for this ID.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:StatusReports(switch) + +
      +
      + +

      Aircraft report status update messages along the route.

      + +

      Parameter

      +
        +
      • + +

        #boolean switch : +Swtich reports on (true) or off (false). No argument is on.

        + +
      • +
      +
      +
      +
      +
      + + #string + +RAT.SubMenuName + +
      +
      + +

      Submenu name for RAT object.

      + +
      +
      +
      +
      + + +RAT:TimeDestroyInactive(time) + +
      +
      + +

      Set the time after which inactive groups will be destroyed.

      + +

      Parameter

      +
        +
      • + +

        #number time : +Time in seconds. Default is 600 seconds = 10 minutes. Minimum is 60 seconds.

        + +
      • +
      +
      +
      +
      +
      + + #number + +RAT.Tinactive + +
      +
      + +

      Time in seconds after which inactive units will be destroyed. Default is 300 seconds.

      + +
      +
      +
      +
      + + +RAT:Uncontrolled() + +
      +
      + +

      Spawn aircraft in uncontolled state.

      + + +

      Aircraft will only sit at their parking spots. Only for populating airfields.

      + +
      +
      +
      +
      + + #number + +RAT.Vclimb + +
      +
      + +

      Default climb rate in ft/min.

      + +
      +
      +
      +
      + + #number + +RAT.Vcruisemax + +
      +
      + +

      Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user.

      + +
      +
      +
      +
      + + +RAT:_ATCAddFlight(name, dest) + +
      +
      + +

      Adds andd initializes a new flight after it was spawned.

      + +

      Parameters

      +
        +
      • + +

        #string name : +Group name of the flight.

        + +
      • +
      • + +

        #string dest : +Name of the destination airport.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCCheck() + +
      +
      + +

      Main ATC function.

      + + +

      Updates the landing queue of all airports and inceases holding time for all flights.

      + +
      +
      +
      +
      + + +RAT:_ATCClearForLanding(airport, flight) + +
      +
      + +

      Giving landing clearance for aircraft by setting user flag.

      + +

      Parameters

      +
        +
      • + +

        #string airport : +Name of destination airport.

        + +
      • +
      • + +

        #string flight : +Group name of flight, which gets landing clearence.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCDelFlight(t, entry) + +
      +
      + +

      Deletes a flight from ATC lists after it landed.

      + +

      Parameters

      +
        +
      • + +

        #table t : +Table.

        + +
      • +
      • + +

        #string entry : +Flight name which shall be deleted.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCFlightLanded(name) + +
      +
      + +

      Takes care of organisational stuff after a plane has landed.

      + +

      Parameter

      +
        +
      • + +

        #string name : +Group name of flight.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCInit(airports_map) + +
      +
      + +

      Initializes the ATC arrays and starts schedulers.

      + +

      Parameter

      +
        +
      • + +

        #table airports_map : +List of all airports of the map.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCQueue() + +
      +
      + +

      Creates a landing queue for all flights holding at airports.

      + + +

      Aircraft with longest holding time gets first permission to land.

      + +
      +
      +
      +
      + + +RAT:_ATCRegisterFlight(name, time) + +
      +
      + +

      Registers a flight once it is near its holding point at the final destination.

      + +

      Parameters

      +
        +
      • + +

        #string name : +Group name of the flight.

        + +
      • +
      • + +

        #number time : +Time the fight first registered.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ATCStatus() + +
      +
      + +

      ATC status report about flights.

      + +
      +
      +
      +
      + + +RAT:_AddFriendlyAirports(ports) + +
      +
      + +

      Add names of all friendly airports to possible departure or destination airports if they are not already in the list.

      + +

      Parameter

      +
        +
      • + +

        #table ports : +List of departure or destination airports/zones that will be added.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_AirportExists(name) + +
      +
      + +

      Test if an airport exists on the current map.

      + +

      Parameter

      +
        +
      • + +

        #string name :

        + +
      • +
      +

      Return value

      + +

      #boolean: +True if airport exsits, false otherwise.

      + +
      +
      +
      +
      + + +RAT:_AnticipatedGroupName(index) + +
      +
      + +

      Anticipated group name from alias and spawn index.

      + +

      Parameter

      +
        +
      • + +

        #number index : +Spawnindex of group if given or self.SpawnIndex+1 by default.

        + +
      • +
      +

      Return value

      + +

      #string: +Name the group will get after it is spawned.

      + +
      +
      +
      +
      + + +RAT:_CheckConsistency() + +
      +
      + +

      Function checks consistency of user input and automatically adjusts parameters if necessary.

      + +
      +
      +
      +
      + + +RAT:_Course(a, b) + +
      +
      + +

      Determine the heading from point a to point b.

      + +

      Parameters

      + +

      Return value

      + +

      #number: +Heading/angle in degrees.

      + +
      +
      +
      +
      + + +RAT:_Debug(switch) + +
      +
      + +

      Turn debug messages on or off.

      + + +

      Default is off.

      + +

      Parameter

      +
        +
      • + +

        #boolean switch : +Turn debug on=true or off=false. No argument means on.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_DeleteMarkers() + +
      +
      + +

      Delete all markers on F10 map.

      + +
      +
      +
      +
      + + +RAT:_Despawn(group) + +
      +
      + +

      Despawn unit.

      + + +

      Unit gets destoyed and group is set to nil. +Index of ratcraft array is taken from spawned group name.

      + +

      Parameter

      + +
      +
      +
      +
      + + +RAT:_EngineStartup(EventData) + +
      +
      + +

      Function is executed when a unit starts its engines.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_Excluded(port) + +
      +
      + +

      Check if airport is excluded from possible departures and destinations.

      + +

      Parameter

      +
        +
      • + +

        #string port : +Name of airport, FARP or ship to check.

        + +
      • +
      +

      Return value

      + +

      #boolean: +true if airport is excluded and false otherwise.

      + +
      +
      +
      +
      + + +RAT:_FLmax(alpha, beta, d, phi, h0) + +
      +
      + +

      Calculate the max flight level for a given distance and fixed climb and descent rates.

      + + +

      In other words we have a distance between two airports and want to know how high we +can climb before we must descent again to arrive at the destination without any level/cruising part.

      + +

      Parameters

      +
        +
      • + +

        #number alpha : +Angle of climb [rad].

        + +
      • +
      • + +

        #number beta : +Angle of descent [rad].

        + +
      • +
      • + +

        #number d : +Distance between the two airports [m].

        + +
      • +
      • + +

        #number phi : +Angle between departure and destination [rad].

        + +
      • +
      • + +

        #number h0 : +Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.

        + +
      • +
      +

      Return value

      + +

      #number: +Maximal flight level in meters.

      + +
      +
      +
      +
      + + +RAT:_GetAirportsInZone(zone) + +
      +
      + +

      Find airports within a zone.

      + +

      Parameter

      + +

      Return value

      + +

      #list: +Table with airport names that lie within the zone.

      + +
      +
      +
      +
      + + +RAT:_GetAirportsOfCoalition() + +
      +
      + +

      Get all "friendly" airports of the current map.

      + +
      +
      +
      +
      + + +RAT:_GetAirportsOfMap() + +
      +
      + +

      Get all airports of the current map.

      + +
      +
      +
      +
      + + +RAT:_GetLife(group) + +
      +
      + +

      Get (relative) life of first unit of a group.

      + +

      Parameter

      + +

      Return value

      + +

      #number: +Life of unit in percent.

      + +
      +
      +
      +
      + + +RAT:_Heading(course) + +
      +
      + +

      Determine the heading for an aircraft to be entered in the route template.

      + +

      Parameter

      +
        +
      • + +

        #number course : +The course between two points in degrees.

        + +
      • +
      +

      Return value

      + +

      #number: +heading Heading in rad.

      + +
      +
      +
      +
      + + +RAT:_InitAircraft(DCSgroup) + +
      +
      + +

      Initialize basic parameters of the aircraft based on its (template) group in the mission editor.

      + +

      Parameter

      + +
      +
      +
      +
      + + +RAT:_IsFriendly(port) + +
      +
      + +

      Check if airport is friendly, i.e.

      + + +

      belongs to the right coalition.

      + +

      Parameter

      +
        +
      • + +

        #string port : +Name of airport, FARP or ship to check.

        + +
      • +
      +

      Return value

      + +

      #boolean: +true if airport is friendly and false otherwise.

      + +
      +
      +
      +
      + + +RAT:_MinDistance(alpha, beta, ha, hb) + +
      +
      + +

      Calculate minimum distance between departure and destination for given minimum flight level and climb/decent rates.

      + +

      Parameters

      +
        +
      • + +

        #number alpha : +Angle of climb [rad].

        + +
      • +
      • + +

        #number beta : +Angle of descent [rad].

        + +
      • +
      • + +

        #number ha : +Height difference between departure and cruise altiude.

        + +
      • +
      • + +

        #number hb : +Height difference between cruise altitude and destination.

        + +
      • +
      +

      Return values

      +
        +
      1. + +

        #number: +d1 Minimum distance for climb phase to reach cruise altitude.

        + +
      2. +
      3. + +

        #number: +d2 Minimum distance for descent phase to reach destination height.

        + +
      4. +
      5. + +

        #number: +dtot Minimum total distance to climb and descent.

        + +
      6. +
      +
      +
      +
      +
      + + +RAT:_ModifySpawnTemplate(waypoints, livery) + +
      +
      + +

      Modifies the template of the group to be spawned.

      + + +

      In particular, the waypoints of the group's flight plan are copied into the spawn template. +This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug".

      + +

      Parameters

      +
        +
      • + +

        #table waypoints : +The waypoints of the AI flight plan.

        + +
      • +
      • + +

        #string livery : +(Optional) Livery of the aircraft. All members of a flight will get the same livery.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_NameInList(liste, name) + +
      +
      + +

      Check if a name/string is in a list or not.

      + +

      Parameters

      +
        +
      • + +

        #table liste : +List of names to be checked.

        + +
      • +
      • + +

        #string name : +Name to be checked for.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnBirth(EventData) + +
      +
      + +

      Function is executed when a unit is spawned.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnCrash(EventData) + +
      +
      + +

      Function is executed when a unit crashes.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnDead(EventData) + +
      +
      + +

      Function is executed when a unit is dead.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnEngineShutdown(EventData) + +
      +
      + +

      Function is executed when a unit shuts down its engines.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnLand(EventData) + +
      +
      + +

      Function is executed when a unit lands.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_OnTakeoff(EventData) + +
      +
      + +

      Function is executed when a unit takes off.

      + +

      Parameter

      +
        +
      • + +

        EventData :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_PickDeparture(takeoff) + +
      +
      + +

      Set the departure airport of the AI.

      + + +

      If no airport name is given explicitly an airport from the coalition is chosen randomly. +If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.

      + +

      Parameter

      +
        +
      • + +

        #number takeoff : +Takeoff type.

        + +
      • +
      +

      Return values

      +
        +
      1. + +

        Wrapper.Airbase#AIRBASE: +Departure airport if spawning at airport.

        + +
      2. +
      3. + +

        Core.Zone#ZONE: +Departure zone if spawning in air.

        + +
      4. +
      +
      +
      +
      +
      + + +RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) + +
      +
      + +

      Pick destination airport or zone depending on departure position.

      + +

      Parameters

      +
        +
      • + +

        Wrapper.Airbase#AIRBASE departure : +Departure airport or zone.

        + +
      • +
      • + +

        Core.Point#COORDINATE q : +Coordinate of the departure point.

        + +
      • +
      • + +

        #number minrange : +Minimum range to q in meters.

        + +
      • +
      • + +

        #number maxrange : +Maximum range to q in meters.

        + +
      • +
      • + +

        #boolean random : +Destination is randomly selected from friendly airport (true) or from destinations specified by user input (false).

        + +
      • +
      • + +

        #number landing : +Number indicating whether we land at a destination airport or fly to a zone object.

        + +
      • +
      +

      Return value

      + +

      Wrapper.Airbase#AIRBASE: +destination Destination airport or zone.

      + +
      +
      +
      +
      + + +RAT:_PlaceMarkers(waypoints, index) + +
      +
      + +

      Place markers of the waypoints.

      + + +

      Note we assume a very specific number and type of waypoints here.

      + +

      Parameters

      +
        +
      • + +

        #table waypoints : +Table with waypoints.

        + +
      • +
      • + +

        #number index : +Spawn index of group.

        + +
      • +
      +
      +
      +
      +
      + + +RAT._Random_Gaussian(x0, sigma, xmin, xmax, self) + +
      +
      + +

      Generate Gaussian pseudo-random numbers.

      + +

      Parameters

      +
        +
      • + +

        #number x0 : +Expectation value of distribution.

        + +
      • +
      • + +

        #number sigma : +(Optional) Standard deviation. Default 10.

        + +
      • +
      • + +

        #number xmin : +(Optional) Lower cut-off value.

        + +
      • +
      • + +

        #number xmax : +(Optional) Upper cut-off value.

        + +
      • +
      • + +

        self :

        + +
      • +
      +

      Return value

      + +

      #number: +Gaussian random number.

      + +
      +
      +
      +
      + + +RAT:_Randomize(value, fac, lower, upper) + +
      +
      + +

      Randomize a value by a certain amount.

      + +

      Parameters

      +
        +
      • + +

        #number value : +The value which should be randomized

        + +
      • +
      • + +

        #number fac : +Randomization factor.

        + +
      • +
      • + +

        #number lower : +(Optional) Lower limit of the returned value.

        + +
      • +
      • + +

        #number upper : +(Optional) Upper limit of the returned value.

        + +
      • +
      +

      Return value

      + +

      #number: +Randomized value.

      + +

      Usages:

      +
        +
      • _Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation.
      • +
      • _Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120.
      • +
      + +
      +
      +
      +
      + + +RAT:_Respawn(group) + +
      +
      + +

      Respawn a group.

      + +

      Parameter

      + +
      +
      +
      +
      + + +RAT:_Routeinfo(waypoints, comment) + +
      +
      + +

      Provide information about the assigned flightplan.

      + +

      Parameters

      +
        +
      • + +

        #table waypoints : +Waypoints of the flight plan.

        + +
      • +
      • + +

        #string comment : +Some comment to identify the provided information.

        + +
      • +
      +

      Return value

      + +

      #number: +total Total route length in meters.

      + +
      +
      +
      +
      + + +RAT:_SetCoalitionTable() + +
      +
      + +

      Create a table with the valid coalitions for departure and destination airports.

      + +
      +
      +
      +
      + + +RAT:_SetMarker(text, wp, index) + +
      +
      + +

      Set a marker visible for all on the F10 map.

      + +

      Parameters

      +
        +
      • + +

        #string text : +Info text displayed at maker.

        + +
      • +
      • + +

        #table wp : +Position of marker coming in as waypoint, i.e. has x, y and alt components.

        + +
      • +
      • + +

        #number index : +Spawn index of group.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_SetROE(group, roe) + +
      +
      + +

      Set ROE for a group.

      + +

      Parameters

      +
        +
      • + +

        Wrapper.Group#GROUP group : +Group for which the ROE is set.

        + +
      • +
      • + +

        #string roe : +ROE of group.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_SetROT(group, rot) + +
      +
      + +

      Set ROT for a group.

      + +

      Parameters

      +
        +
      • + +

        Wrapper.Group#GROUP group : +Group for which the ROT is set.

        + +
      • +
      • + +

        #string rot : +ROT of group.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_SetRoute(Takeoff, Landing, _departure, _destination, takeoff, landing, _waypoint) + +
      +
      + +

      Set the route of the AI plane.

      + + +

      Due to DCS landing bug, this has to be done before the unit is spawned.

      + +

      Parameters

      + +

      Return values

      +
        +
      1. + +

        Wrapper.Airport#AIRBASE: +Departure airbase.

        + +
      2. +
      3. + +

        Wrapper.Airport#AIRBASE: +Destination airbase.

        + +
      4. +
      5. + +

        #table: +Table of flight plan waypoints.

        + +
      6. +
      7. + +

        #nil: +If no valid departure or destination airport could be found.

        + +
      8. +
      +
      +
      +
      +
      + + +RAT:_SetStatus(group, status) + +
      +
      + +

      Set status of group.

      + +

      Parameters

      +
        +
      • + +

        group :

        + +
      • +
      • + +

        status :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint) + +
      +
      + +

      Spawn the AI aircraft with a route.

      + + +

      Sets the departure and destination airports and waypoints. +Modifies the spawn template. +Sets ROE/ROT. +Initializes the ratcraft array and group menu.

      + +

      Parameters

      +
        +
      • + +

        #string _departure : +(Optional) Name of departure airbase.

        + +
      • +
      • + +

        #string _destination : +(Optional) Name of destination airbase.

        + +
      • +
      • + +

        #number _takeoff : +Takeoff type id.

        + +
      • +
      • + +

        _landing :

        + +
      • +
      • + +

        _livery :

        + +
      • +
      • + +

        _waypoint :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_TaskFunction(FunctionString, ...) + +
      +
      + +

      Task function.

      + +

      Parameters

      +
        +
      • + +

        #string FunctionString : +Name of the function to be called.

        + +
      • +
      • + +

        ... :

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_TaskHolding(P1, Altitude, Speed, Duration) + +
      +
      + +

      Orbit at a specified position at a specified alititude with a specified speed.

      + +

      Parameters

      +
        +
      • + +

        Dcs.DCSTypes#Vec2 P1 : +The point to hold the position.

        + +
      • +
      • + +

        #number Altitude : +The altitude ASL at which to hold the position.

        + +
      • +
      • + +

        #number Speed : +The speed flying when holding the position in m/s.

        + +
      • +
      • + +

        #number Duration : +Duration of holding pattern in seconds.

        + +
      • +
      +

      Return value

      + +

      Dcs.DCSTasking.Task#Task: +DCSTask

      + +
      +
      +
      +
      + + +RAT:_Waypoint(Running, Type, Coord, Speed, Altitude, Airport, index) + +
      +
      + +

      Create a waypoint that can be used with the Route command.

      + +

      Parameters

      +
        +
      • + +

        #number Running : +index of waypoints. Starts with 1 which is normally departure/spawn waypoint.

        + +
      • +
      • + +

        #number Type : +Type of waypoint.

        + +
      • +
      • + +

        Core.Point#COORDINATE Coord : +3D coordinate of the waypoint.

        + +
      • +
      • + +

        #number Speed : +Speed in m/s.

        + +
      • +
      • + +

        #number Altitude : +Altitude in m.

        + +
      • +
      • + +

        Wrapper.Airbase#AIRBASE Airport : +Airport of object to spawn.

        + +
      • +
      • + +

        index :

        + +
      • +
      +

      Return value

      + +

      #table: +Waypoints for DCS task route or spawn template.

      + +
      +
      +
      +
      + + +RAT._WaypointFunction(group, rat, wp) + +
      +
      + +

      Function which is called after passing every waypoint.

      + + +

      Info on waypoint is given and special functions are executed.

      + +

      Parameters

      +
        +
      • + +

        Core.Group#GROUP group : +Group of aircraft.

        + +
      • +
      • + +

        #RAT rat : +RAT object.

        + +
      • +
      • + +

        #number wp : +Waypoint index. Running number of the waypoints. Determines the actions to be executed.

        + +
      • +
      +
      +
      +
      +
      + + +RAT:_ZoneExists(name) + +
      +
      + +

      Test if a trigger zone defined in the mission editor exists.

      + +

      Parameter

      +
        +
      • + +

        #string name :

        + +
      • +
      +

      Return value

      + +

      #boolean: +True if zone exsits, false otherwise.

      + +
      +
      +
      +
      + + + +RAT.actype + +
      +
      + + + +
      +
      +
      +
      + + #boolean + +RAT.addfriendlydepartures + +
      +
      + +

      Add all friendly airports to departures.

      + +
      +
      +
      +
      + + #boolean + +RAT.addfriendlydestinations + +
      +
      + +

      Add all friendly airports to destinations.

      + +
      +
      +
      +
      + + #table + +RAT.aircraft + +
      +
      + +

      Table which holds the basic aircraft properties (speed, range, ...).

      + +
      +
      +
      +
      + + #table + +RAT.airports + +
      +
      + +

      All airports of friedly coalitions.

      + +
      +
      +
      +
      + + #table + +RAT.airports_map + +
      +
      + +

      All airports available on current map (Caucasus, Nevada, Normandy, ...).

      + +
      +
      +
      +
      + + #string + +RAT.alias + +
      +
      + +

      Alias for spawned group.

      + +
      +
      +
      +
      + + #number + +RAT.alive + +
      +
      + +

      Number of groups which are alive.

      + +
      +
      +
      +
      + + + +RAT.cat + +
      +
      + + + +
      +
      +
      +
      + + #string + +RAT.category + +
      +
      + +

      Category of aircarft: "plane" or "heli".

      + +
      +
      +
      +
      + + + +RAT.coal + +
      +
      + +

      RAT friendly coalitions.

      + +
      +
      +
      +
      + + #number + +RAT.coalition + +
      +
      + +

      Coalition of spawn group template.

      + +
      +
      +
      +
      + + #boolean + +RAT.commute + +
      +
      + +

      Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.

      + +
      +
      +
      +
      + + #boolean + +RAT.continuejourney + +
      +
      + +

      Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.

      + +
      +
      +
      +
      + + #number + +RAT.country + +
      +
      + +

      Country of spawn group template.

      + +
      +
      +
      +
      + + #table + +RAT.ctable + +
      +
      + +

      Table with the valid coalitons from choice self.friendly.

      + +
      +
      +
      +
      + + #boolean + +RAT.debug + +
      +
      + +

      Turn debug messages on or off.

      + +
      +
      +
      +
      + + Core.Zone#ZONE + +RAT.departure_Azone + +
      +
      + +

      Zone containing the departure airports.

      + +
      +
      +
      +
      + + #table + +RAT.departure_ports + +
      +
      + +

      Array containing the names of the destination airports or zones.

      + +
      +
      +
      +
      + + Core.Zone#ZONE + +RAT.destination_Azone + +
      +
      + +

      Zone containing the destination airports.

      + +
      +
      +
      +
      + + #table + +RAT.destination_ports + +
      +
      + +

      Array containing the names of the destination airports or zones.

      + +
      +
      +
      +
      + + #boolean + +RAT.destinationzone + +
      +
      + +

      Destination is a zone and not an airport.

      + +
      +
      +
      +
      + + #table + +RAT.excluded_ports + +
      +
      + +

      Array containing the names of explicitly excluded airports.

      + +
      +
      +
      +
      + + #boolean + +RAT.f10menu + +
      +
      + +

      Add an F10 menu for RAT.

      + +
      +
      +
      +
      + + #number + +RAT.frequency + +
      +
      + +

      Radio frequency used by the RAT groups.

      + +
      +
      +
      +
      + + #string + +RAT.friendly + +
      +
      + +

      Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red.

      + +
      +
      +
      +
      + + #string + +RAT.id + +
      +
      + +

      Some ID to identify who we are in output of the DCS.log file.

      + +
      +
      +
      +
      + + #number + +RAT.landing + +
      +
      + +

      Landing type. Determines if we actually land at an airport or treat it as zone.

      + +
      +
      +
      +
      + + #string + +RAT.livery + +
      +
      + +

      Livery of the aircraft set by user.

      + +
      +
      +
      +
      + + #number + +RAT.markerid + +
      +
      + +

      Running number of placed markers on the F10 map.

      + +
      +
      +
      +
      + + #table + +RAT.markerids + +
      +
      + +

      Array with marker IDs.

      + +
      +
      +
      +
      + + #number + +RAT.maxdist + +
      +
      + +

      Max distance from departure to destination in meters. Default 5000 km.

      + +
      +
      +
      +
      + + #number + +RAT.mindist + +
      +
      + +

      Min distance from departure to destination in meters. Default 5 km.

      + +
      +
      +
      +
      + + #string + +RAT.modulation + +
      +
      + +

      Ratio modulation. Either "FM" or "AM".

      + +
      +
      +
      +
      + + #number + +RAT.ngroups + +
      +
      + +

      Number of groups to be spawned in total.

      + +
      +
      +
      +
      + + #boolean + +RAT.norespawn + +
      +
      + +

      Aircraft will not be respawned after they have finished their route.

      + +
      +
      +
      +
      + + + +RAT.onboard_num + +
      +
      + + + + +

      Onboard number.

      + +
      +
      +
      +
      + + #string + +RAT.parking_id + +
      +
      + +

      String with a special parking ID for the aircraft.

      + +
      +
      +
      +
      + + #boolean + +RAT.placemarkers + +
      +
      + +

      Place markers of waypoints on F10 map.

      + +
      +
      +
      +
      + + #boolean + +RAT.radio + +
      +
      + +

      If true/false disables radio messages from the RAT groups.

      + +
      +
      +
      +
      + + #boolean + +RAT.random_departure + +
      +
      + +

      By default a random friendly airport is chosen as departure.

      + +
      +
      +
      +
      + + #boolean + +RAT.random_destination + +
      +
      + +

      By default a random friendly airport is chosen as destination.

      + +
      +
      +
      +
      + + #table + +RAT.ratcraft + +
      +
      + +

      Array with the spawned RAT aircraft.

      + +
      +
      +
      +
      + + #boolean + +RAT.reportstatus + +
      +
      + +

      Aircraft report status.

      + +
      +
      +
      +
      + + #boolean + +RAT.respawn_after_takeoff + +
      +
      + +

      Aircraft will be respawned directly after take-off.

      + +
      +
      +
      +
      + + #boolean + +RAT.respawn_at_landing + +
      +
      + +

      Respawn aircraft the moment they land rather than at engine shutdown.

      + +
      +
      +
      +
      + + #number + +RAT.respawn_delay + +
      +
      + +

      Delay in seconds until repawn happens after landing.

      + +
      +
      +
      +
      + + #table + +RAT.return_zones + +
      +
      + +

      Array containing the names of the return zones.

      + +
      +
      +
      +
      + + #boolean + +RAT.returnzone + +
      +
      + +

      Zone where aircraft will fly to before returning to their departure airport.

      + +
      +
      +
      +
      + + #string + +RAT.roe + +
      +
      + +

      ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free".

      + +
      +
      +
      +
      + + #string + +RAT.rot + +
      +
      + +

      ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade".

      + +
      +
      +
      +
      + + #string + +RAT.skill + +
      +
      + +

      Skill of AI.

      + +
      +
      +
      +
      + + #number + +RAT.spawndelay + +
      +
      + +

      Delay time in seconds before first spawning happens.

      + +
      +
      +
      +
      + + #boolean + +RAT.spawninitialized + +
      +
      + +

      If RAT:Spawn() was already called this RAT object is set to true to prevent users to call it again.

      + +
      +
      +
      +
      + + #number + +RAT.spawninterval + +
      +
      + +

      Interval between spawning units/groups. Note that we add a randomization of 50%.

      + +
      +
      +
      +
      + + + +RAT.status + +
      +
      + +

      RAT aircraft status.

      + +
      +
      +
      +
      + + #number + +RAT.statusinterval + +
      +
      + +

      Intervall between status checks (and reports if enabled).

      + +
      +
      +
      +
      + + #number + +RAT.takeoff + +
      +
      + +

      Takeoff type. 0=coldorhot.

      + +
      +
      +
      +
      + + Core.Group#GROUP + +RAT.templategroup + +
      +
      + +

      Group serving as template for the RAT aircraft.

      + +
      +
      +
      +
      + + #boolean + +RAT.uncontrolled + +
      +
      + +

      If true aircraft are spawned in uncontrolled state and will only sit on their parking spots.

      + +
      +
      +
      +
      + + + +RAT.unit + +
      +
      + +

      RAT unit conversions.

      + +
      +
      +
      +
      + + #string + +RAT.version + +
      +
      + +

      RAT version.

      + +
      +
      +
      +
      + + #table + +RAT.waypointdescriptions + +
      +
      + +

      Table with strings for waypoint descriptions of markers.

      + +
      +
      +
      +
      + + #table + +RAT.waypointstatus + +
      +
      + +

      Table with strings of waypoint status.

      + +
      +
      +
      +
      + + + +RAT.wp + +
      +
      + +

      RAT waypoint type.

      + +
      +
      + +

      Type list

      + +
      + +
      + + diff --git a/docs/Documentation/Route.html b/docs/Documentation/Route.html index d562152e5..5588ac2f1 100644 --- a/docs/Documentation/Route.html +++ b/docs/Documentation/Route.html @@ -28,9 +28,9 @@
    201. AI_Cas
    202. AI_Formation
    203. AI_Patrol
    204. +
    205. ATC_Ground
    206. Account
    207. Airbase
    208. -
    209. AirbasePolice
    210. Assign
    211. Base
    212. Cargo
    213. @@ -60,6 +60,7 @@
    214. Escort
    215. Event
    216. Fsm
    217. +
    218. Goal
    219. Group
    220. Identifiable
    221. Menu
    222. @@ -72,7 +73,9 @@
    223. Positionable
    224. Process_JTAC
    225. Process_Pickup
    226. +
    227. Protect
    228. Radio
    229. +
    230. Rat
    231. Route
    232. Scenery
    233. ScheduleDispatcher
    234. @@ -88,6 +91,7 @@
    235. Static
    236. StaticObject
    237. Task
    238. +
    239. TaskZoneCapture
    240. Task_A2A
    241. Task_A2A_Dispatcher
    242. Task_A2G
    243. @@ -95,8 +99,15 @@
    244. Task_Cargo
    245. Task_PICKUP
    246. Unit
    247. +
    248. UserFlag
    249. +
    250. UserSound
    251. Utils
    252. +
    253. Velocity
    254. Zone
    255. +
    256. ZoneCaptureCoalition
    257. +
    258. ZoneGoal
    259. +
    260. ZoneGoalCargo
    261. +
    262. ZoneGoalCoalition
    263. env
    264. land
    265. routines
    266. @@ -616,7 +627,7 @@ Upon arrival at the zone, a confirmation of arrival is sent, and the process wil @@ -808,7 +819,7 @@ self

    +
    +
    +
    + + + +SCHEDULER.MasterObject + +
    +
    + + +
    diff --git a/docs/Documentation/Scoring.html b/docs/Documentation/Scoring.html index 1bf9e8bd7..f395d533f 100644 --- a/docs/Documentation/Scoring.html +++ b/docs/Documentation/Scoring.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -216,7 +227,9 @@ just large enough around that building.

    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.
    @@ -366,6 +379,12 @@ Various methods exist to configure:

    SCORING:AddGoalScore(PlayerUnit, GoalTag, Text, Score)

    Add a goal score for a player.

    + + + + SCORING:AddGoalScorePlayer(PlayerName, GoalTag, Text, Score) + +

    Add a goal score for a player.

    @@ -414,6 +433,12 @@ Various methods exist to configure:

    SCORING.CoalitionChangePenalty + + + + SCORING.DisplayMessagePrefix + + @@ -642,6 +667,12 @@ Various methods exist to configure:

    SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty)

    When a player changes the coalition, he can receive a penalty score.

    + + + + SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) + +

    Set a prefix string that will be displayed at each scoring message sent.

    @@ -696,6 +727,12 @@ Various methods exist to configure:

    SCORING:SetScaleDestroyScore(Scale)

    Set the scale for scoring valid destroys (enemy destroys).

    + + + + SCORING:_AddMissionGoalScore(Mission, PlayerName, Text, Score) + +

    Registers Scores the players completing a Mission Task.

    @@ -843,6 +880,52 @@ The score can be both positive or negative ( Penalty ).

    + +SCORING:AddGoalScorePlayer(PlayerName, GoalTag, Text, Score) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #string PlayerName : +The name of the Player.

      + +
    • +
    • + +

      #string GoalTag : +The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel).

      + +
    • +
    • + +

      #string Text : +A free text that is shown to the players.

      + +
    • +
    • + +

      #number Score : +The score can be both positive or negative ( Penalty ).

      + +
    • +
    +
    +
    +
    +
    + SCORING:AddScoreGroup(ScoreGroup, Score) @@ -1038,6 +1121,19 @@ The Score value.

    + +
    +
    +
    + + +SCORING.DisplayMessagePrefix + +
    +
    + + +
    @@ -1889,6 +1985,33 @@ The amount of penalty that is given.

    #SCORING:

    + +
    +
    +
    + + +SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) + +
    +
    + +

    Set a prefix string that will be displayed at each scoring message sent.

    + +

    Parameter

    +
      +
    • + +

      #string DisplayMessagePrefix : +(Default="Scoring: ") The scoring prefix string.

      + +
    • +
    +

    Return value

    + +

    #SCORING:

    + +
    @@ -2126,6 +2249,42 @@ The scale of the score given.

    + +SCORING:_AddMissionGoalScore(Mission, PlayerName, Text, Score) + +
    +
    + +

    Registers Scores the players completing a Mission Task.

    + +

    Parameters

    +
      +
    • + +

      Tasking.Mission#MISSION Mission :

      + +
    • +
    • + +

      #string PlayerName :

      + +
    • +
    • + +

      #string Text :

      + +
    • +
    • + +

      #number Score :

      + +
    • +
    +
    +
    +
    +
    + SCORING:_AddMissionScore(Mission, PlayerUnit, Text, Score) diff --git a/docs/Documentation/Sead.html b/docs/Documentation/Sead.html index a1ac3dd2e..ef7ee8a05 100644 --- a/docs/Documentation/Sead.html +++ b/docs/Documentation/Sead.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Set.html b/docs/Documentation/Set.html index cde3e33b5..020a07e22 100644 --- a/docs/Documentation/Set.html +++ b/docs/Documentation/Set.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -226,6 +237,38 @@
  • Countries
  • Starting with certain prefix strings.
  • + + + + SET_PLAYER + +

    4) SET_PLAYER class, extends Set#SET_BASE

    + +

    Mission designers can use the Set#SET_PLAYER class to build sets of units belonging to alive players:

    + +

    4.1) SET_PLAYER constructor

    + +

    Create a new SET_PLAYER object with the SET_PLAYER.New method:

    + + + + + + SET_STATIC + +

    3) SET_STATIC class, extends Set#SET_BASE

    + +

    Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain:

    + +
      +
    • Coalitions
    • +
    • Categories
    • +
    • Countries
    • +
    • Static types
    • +
    • Starting with certain prefix strings.
    • +
    @@ -435,6 +478,18 @@ SET_BASE:GetSet()

    Gets the Set.

    + + + + SET_BASE:GetSetNames() + +

    Gets a list of the Names of the Objects in the Set.

    + + + + SET_BASE:GetSetObjects() + +

    Gets a list of the Objects in the Set.

    @@ -501,18 +556,6 @@ SET_BASE:_EventOnDeadOrCrash(Event)

    Handles the OnDead or OnCrash event for alive units set.

    - - - - SET_BASE:_EventOnPlayerEnterUnit(Event) - -

    Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied).

    - - - - SET_BASE:_EventOnPlayerLeaveUnit(Event) - -

    Handles the OnPlayerLeaveUnit event to clean the active players table.

    @@ -907,6 +950,278 @@ mission designer to add a dedicated method

    SET_GROUP:RemoveGroupsByName(RemoveGroupNames)

    Remove GROUP(s) from SET_GROUP.

    + + + + SET_GROUP:_EventOnDeadOrCrash(Event) + +

    Handles the OnDead or OnCrash event for alive groups set.

    + + + + +

    Type SET_PLAYER

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SET_PLAYER:AddClientsByName(AddClientNames) +

    Add CLIENT(s) to SET_PLAYER.

    +
    SET_PLAYER:AddInDatabase(Event) +

    Handles the Database to check on an event (birth) that the Object was added in the Database.

    +
    SET_PLAYER:FilterCategories(Categories) +

    Builds a set of clients out of categories joined by players.

    +
    SET_PLAYER:FilterCoalitions(Coalitions) +

    Builds a set of clients of coalitions joined by specific players.

    +
    SET_PLAYER:FilterCountries(Countries) +

    Builds a set of clients of defined countries.

    +
    SET_PLAYER:FilterPrefixes(Prefixes) +

    Builds a set of clients of defined client prefixes.

    +
    SET_PLAYER:FilterStart() +

    Starts the filtering.

    +
    SET_PLAYER:FilterTypes(Types) +

    Builds a set of clients of defined client types joined by players.

    +
    SET_PLAYER:FindClient(PlayerName) +

    Finds a Client based on the Player Name.

    +
    SET_PLAYER:FindInDatabase(Event) +

    Handles the Database to check on any event that Object exists in the Database.

    +
    SET_PLAYER:ForEachPlayer(IteratorFunction, ...) +

    Iterate the SET_PLAYER and call an interator function for each alive CLIENT, providing the CLIENT and optional parameters.

    +
    SET_PLAYER:ForEachPlayerInZone(ZoneObject, IteratorFunction, ...) +

    Iterate the SET_PLAYER and call an iterator function for each alive CLIENT presence completely in a Zone, providing the CLIENT and optional parameters to the called function.

    +
    SET_PLAYER:ForEachPlayerNotInZone(ZoneObject, IteratorFunction, ...) +

    Iterate the SET_PLAYER and call an iterator function for each alive CLIENT presence not in a Zone, providing the CLIENT and optional parameters to the called function.

    +
    SET_PLAYER:IsIncludeObject(MClient) + +
    SET_PLAYER:New() +

    Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names.

    +
    SET_PLAYER:RemoveClientsByName(RemoveClientNames) +

    Remove CLIENT(s) from SET_PLAYER.

    +
    + +

    Type SET_STATIC

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SET_STATIC:AddInDatabase(Event) +

    Handles the Database to check on an event (birth) that the Object was added in the Database.

    +
    SET_STATIC:AddStatic(AddStatic) +

    Add STATIC(s) to SET_STATIC.

    +
    SET_STATIC:AddStaticsByName(AddStaticNames) +

    Add STATIC(s) to SET_STATIC.

    +
    SET_STATIC:FilterCategories(Categories) +

    Builds a set of units out of categories.

    +
    SET_STATIC:FilterCoalitions(Coalitions) +

    Builds a set of units of coalitions.

    +
    SET_STATIC:FilterCountries(Countries) +

    Builds a set of units of defined countries.

    +
    SET_STATIC:FilterPrefixes(Prefixes) +

    Builds a set of units of defined unit prefixes.

    +
    SET_STATIC:FilterStart() +

    Starts the filtering.

    +
    SET_STATIC:FilterTypes(Types) +

    Builds a set of units of defined unit types.

    +
    SET_STATIC:FindInDatabase(Event) +

    Handles the Database to check on any event that Object exists in the Database.

    +
    SET_STATIC:FindStatic(StaticName) +

    Finds a Static based on the Static Name.

    +
    SET_STATIC:ForEachStatic(IteratorFunction, ...) +

    Iterate the SET_STATIC and call an interator function for each alive STATIC, providing the STATIC and optional parameters.

    +
    SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject, IteratorFunction, ...) +

    Iterate the SET_STATIC and call an iterator function for each alive STATIC presence completely in a Zone, providing the STATIC and optional parameters to the called function.

    +
    SET_STATIC:ForEachStaticInZone(IteratorFunction, ...) +

    Check if minimal one element of the SET_STATIC is in the Zone.

    +
    SET_STATIC:ForEachStaticNotInZone(ZoneObject, IteratorFunction, ...) +

    Iterate the SET_STATIC and call an iterator function for each alive STATIC presence not in a Zone, providing the STATIC and optional parameters to the called function.

    +
    SET_STATIC:GetCoordinate() +

    Get the center coordinate of the SET_STATIC.

    +
    SET_STATIC:GetFirst() +

    Get the first unit from the set.

    +
    SET_STATIC:GetHeading() +

    Get the average heading of the SET_STATIC.

    +
    SET_STATIC:GetStaticTypes() +

    Returns map of unit types.

    +
    SET_STATIC:GetStaticTypesText() +

    Returns a comma separated string of the unit types with a count in the Set.

    +
    SET_STATIC:GetTypeNames(Delimiter) +

    Retrieve the type names of the Statics in the SET, delimited by an optional delimiter.

    +
    SET_STATIC:GetVelocity() +

    Get the maximum velocity of the SET_STATIC.

    +
    SET_STATIC:IsIncludeObject(MStatic) + +
    SET_STATIC:IsNotInZone(ZoneObject, Zone) +

    Check if no element of the SET_STATIC is in the Zone.

    +
    SET_STATIC:IsPatriallyInZone(Zone) +

    Check if minimal one element of the SET_STATIC is in the Zone.

    +
    SET_STATIC:New() +

    Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.

    +
    SET_STATIC:RemoveStaticsByName(RemoveStaticNames) +

    Remove STATIC(s) from SET_STATIC.

    @@ -1007,6 +1322,12 @@ mission designer to add a dedicated method

    SET_UNIT:ForEachUnitCompletelyInZone(ZoneObject, IteratorFunction, ...)

    Iterate the SET_UNIT and call an iterator function for each alive UNIT presence completely in a Zone, providing the UNIT and optional parameters to the called function.

    + + + + SET_UNIT:ForEachUnitInZone(IteratorFunction, ...) + +

    Check if minimal one element of the SET_UNIT is in the Zone.

    @@ -1020,12 +1341,24 @@ mission designer to add a dedicated method

    Iterate the SET_UNIT sorted *per Threat Level and call an interator function for each alive UNIT, providing the UNIT and optional parameters.

    + + + + SET_UNIT:GetCoordinate() + +

    Get the center coordinate of the SET_UNIT.

    SET_UNIT:GetFirst()

    Get the first unit from the set.

    + + + + SET_UNIT:GetHeading() + +

    Get the average heading of the SET_UNIT.

    @@ -1050,6 +1383,12 @@ mission designer to add a dedicated method

    SET_UNIT:GetUnitTypesText()

    Returns a comma separated string of the unit types with a count in the Set.

    + + + + SET_UNIT:GetVelocity() + +

    Get the maximum velocity of the SET_UNIT.

    @@ -1080,6 +1419,18 @@ mission designer to add a dedicated method

    SET_UNIT:IsIncludeObject(MUnit) + + + + SET_UNIT:IsNotInZone(ZoneObject, Zone) + +

    Check if no element of the SET_UNIT is in the Zone.

    + + + + SET_UNIT:IsPartiallyInZone(ZoneTest) + +

    Check if minimal one element of the SET_UNIT is in the Zone.

    @@ -1418,6 +1769,163 @@ The following iterator methods are currently available within the SET
    GROUP:
    + +
    +
    +
    + + #SET_PLAYER + +SET_PLAYER + +
    +
    + +

    4) SET_PLAYER class, extends Set#SET_BASE

    + +

    Mission designers can use the Set#SET_PLAYER class to build sets of units belonging to alive players:

    + +

    4.1) SET_PLAYER constructor

    + +

    Create a new SET_PLAYER object with the SET_PLAYER.New method:

    + + + + +

    +

    4.3) SET_PLAYER filter criteria

    + +

    You can set filter criteria to define the set of clients within the SET_PLAYER. +Filter criteria are defined by:

    + + + +

    Once the filter criteria have been set for the SET_PLAYER, you can start filtering using:

    + + + +

    Planned filter criteria within development are (so these are not yet available):

    + + + +

    4.4) SET_PLAYER iterators

    + +

    Once the filters have been defined and the SETPLAYER has been built, you can iterate the SETPLAYER with the available iterator methods. +The iterator methods will walk the SETPLAYER set, and call for each element within the set a function that you provide. +The following iterator methods are currently available within the SETPLAYER:

    + + + +
    + +
    +
    +
    +
    + + #SET_STATIC + +SET_STATIC + +
    +
    + +

    3) SET_STATIC class, extends Set#SET_BASE

    + +

    Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain:

    + +
      +
    • Coalitions
    • +
    • Categories
    • +
    • Countries
    • +
    • Static types
    • +
    • Starting with certain prefix strings.
    • +
    + + +

    +

    3.1) SET_STATIC constructor

    + +

    Create a new SET_STATIC object with the SET_STATIC.New method:

    + + + +

    3.2) Add or Remove STATIC(s) from SET_STATIC

    + +

    STATICs can be added and removed using the Set#SET_STATIC.AddStaticsByName and Set#SET_STATIC.RemoveStaticsByName respectively. +These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC.

    + +

    3.3) SET_STATIC filter criteria

    + +

    You can set filter criteria to define the set of units within the SET_STATIC. +Filter criteria are defined by:

    + + + +

    Once the filter criteria have been set for the SET_STATIC, you can start filtering using:

    + + + +

    Planned filter criteria within development are (so these are not yet available):

    + + + +

    3.4) SET_STATIC iterators

    + +

    Once the filters have been defined and the SETSTATIC has been built, you can iterate the SETSTATIC with the available iterator methods. +The iterator methods will walk the SETSTATIC set, and call for each element within the set a function that you provide. +The following iterator methods are currently available within the SETSTATIC:

    + +
      +
    • SET_STATIC.ForEachStatic: Calls a function for each alive unit it finds within the SET_STATIC.
    • +
    • SET_GROUP.ForEachGroupCompletelyInZone: Iterate the SET_GROUP and call an iterator function for each alive GROUP presence completely in a Zone, providing the GROUP and optional parameters to the called function.
    • +
    • SET_GROUP.ForEachGroupNotInZone: Iterate the SET_GROUP and call an iterator function for each alive GROUP presence not in a Zone, providing the GROUP and optional parameters to the called function.
    • +
    + +

    Planned iterators methods in development are (so these are not yet available):

    + + + +

    3.5 ) SET_STATIC atomic methods

    + +

    Various methods exist for a SETSTATIC to perform actions or calculations and retrieve results from the SETSTATIC:

    + + + +
    +
    @@ -2270,6 +2778,42 @@ self

    + +SET_BASE:GetSetNames() + +
    +
    + +

    Gets a list of the Names of the Objects in the Set.

    + +

    Return value

    + +

    #SET_BASE: +self

    + +
    +
    +
    +
    + + +SET_BASE:GetSetObjects() + +
    +
    + +

    Gets a list of the Objects in the Set.

    + +

    Return value

    + +

    #SET_BASE: +self

    + +
    +
    +
    +
    + SET_BASE:IsIncludeObject(Object) @@ -2504,48 +3048,6 @@ self

    - -SET_BASE:_EventOnPlayerEnterUnit(Event) - -
    -
    - -

    Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied).

    - -

    Parameter

    - -
    -
    -
    -
    - - -SET_BASE:_EventOnPlayerLeaveUnit(Event) - -
    -
    - -

    Handles the OnPlayerLeaveUnit event to clean the active players table.

    - -

    Parameter

    - -
    -
    -
    -
    - SET_BASE:_FilterStart() @@ -2957,8 +3459,8 @@ self

    Return value

    -

    #SET_CARGO: -self

    +

    #SET_CARGO:

    +

    Usage:

    -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos.
    @@ -4426,6 +4928,1253 @@ A single name or an array of GROUP names.

    Return value

    +

    self

    + + +
    +
    +
    + + +SET_GROUP:_EventOnDeadOrCrash(Event) + +
    +
    + +

    Handles the OnDead or OnCrash event for alive groups set.

    + + +

    Note: The GROUP object in the SET_GROUP collection will only be removed if the last unit is destroyed of the GROUP.

    + +

    Parameter

    + +
    +
    + +

    Type SET_PLAYER

    +

    Field(s)

    +
    +
    + + +SET_PLAYER:AddClientsByName(AddClientNames) + +
    +
    + +

    Add CLIENT(s) to SET_PLAYER.

    + +

    Parameter

    +
      +
    • + +

      #string AddClientNames : +A single name or an array of CLIENT names.

      + +
    • +
    +

    Return value

    + + +

    self

    + +
    +
    +
    +
    + + +SET_PLAYER:AddInDatabase(Event) + +
    +
    + +

    Handles the Database to check on an event (birth) that the Object was added in the Database.

    + + +

    This is required, because sometimes the DATABASE birth event gets called later than the SETBASE birth event!

    + +

    Parameter

    + +

    Return values

    +
      +
    1. + +

      #string: +The name of the CLIENT

      + +
    2. +
    3. + +

      #table: +The CLIENT

      + +
    4. +
    +
    +
    +
    +
    + + +SET_PLAYER:FilterCategories(Categories) + +
    +
    + +

    Builds a set of clients out of categories joined by players.

    + + +

    Possible current categories are plane, helicopter, ground, ship.

    + +

    Parameter

    +
      +
    • + +

      #string Categories : +Can take the following values: "plane", "helicopter", "ground", "ship".

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FilterCoalitions(Coalitions) + +
    +
    + +

    Builds a set of clients of coalitions joined by specific players.

    + + +

    Possible current coalitions are red, blue and neutral.

    + +

    Parameter

    +
      +
    • + +

      #string Coalitions : +Can take the following values: "red", "blue", "neutral".

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FilterCountries(Countries) + +
    +
    + +

    Builds a set of clients of defined countries.

    + + +

    Possible current countries are those known within DCS world.

    + +

    Parameter

    +
      +
    • + +

      #string Countries : +Can take those country strings known within DCS world.

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FilterPrefixes(Prefixes) + +
    +
    + +

    Builds a set of clients of defined client prefixes.

    + + +

    All the clients starting with the given prefixes will be included within the set.

    + +

    Parameter

    +
      +
    • + +

      #string Prefixes : +The prefix of which the client name starts with.

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FilterStart() + +
    +
    + +

    Starts the filtering.

    + +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FilterTypes(Types) + +
    +
    + +

    Builds a set of clients of defined client types joined by players.

    + + +

    Possible current types are those types known within DCS world.

    + +

    Parameter

    +
      +
    • + +

      #string Types : +Can take those type strings known within DCS world.

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:FindClient(PlayerName) + +
    +
    + +

    Finds a Client based on the Player Name.

    + +

    Parameter

    +
      +
    • + +

      #string PlayerName :

      + +
    • +
    +

    Return value

    + +

    Wrapper.Client#CLIENT: +The found Client.

    + +
    +
    +
    +
    + + +SET_PLAYER:FindInDatabase(Event) + +
    +
    + +

    Handles the Database to check on any event that Object exists in the Database.

    + + +

    This is required, because sometimes the DATABASE event gets called later than the SETBASE event or vise versa!

    + +

    Parameter

    + +

    Return values

    +
      +
    1. + +

      #string: +The name of the CLIENT

      + +
    2. +
    3. + +

      #table: +The CLIENT

      + +
    4. +
    +
    +
    +
    +
    + + +SET_PLAYER:ForEachPlayer(IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_PLAYER and call an interator function for each alive CLIENT, providing the CLIENT and optional parameters.

    + +

    Parameters

    +
      +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:ForEachPlayerInZone(ZoneObject, IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_PLAYER and call an iterator function for each alive CLIENT presence completely in a Zone, providing the CLIENT and optional parameters to the called function.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:ForEachPlayerNotInZone(ZoneObject, IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_PLAYER and call an iterator function for each alive CLIENT presence not in a Zone, providing the CLIENT and optional parameters to the called function.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:IsIncludeObject(MClient) + +
    +
    + + + +

    Parameter

    + +

    Return value

    + +

    #SET_PLAYER: +self

    + +
    +
    +
    +
    + + +SET_PLAYER:New() + +
    +
    + +

    Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names.

    + +

    Return value

    + +

    #SET_PLAYER:

    + + +

    Usage:

    +
    -- Define a new SET_PLAYER Object. This DBObject will contain a reference to all Clients.
    +DBObject = SET_PLAYER:New()
    + +
    +
    +
    +
    + + +SET_PLAYER:RemoveClientsByName(RemoveClientNames) + +
    +
    + +

    Remove CLIENT(s) from SET_PLAYER.

    + +

    Parameter

    + +

    Return value

    + + +

    self

    + +
    +
    + +

    Type SET_STATIC

    +

    Field(s)

    +
    +
    + + +SET_STATIC:AddInDatabase(Event) + +
    +
    + +

    Handles the Database to check on an event (birth) that the Object was added in the Database.

    + + +

    This is required, because sometimes the DATABASE birth event gets called later than the SETBASE birth event!

    + +

    Parameter

    + +

    Return values

    +
      +
    1. + +

      #string: +The name of the STATIC

      + +
    2. +
    3. + +

      #table: +The STATIC

      + +
    4. +
    +
    +
    +
    +
    + + +SET_STATIC:AddStatic(AddStatic) + +
    +
    + +

    Add STATIC(s) to SET_STATIC.

    + +

    Parameter

    +
      +
    • + +

      #string AddStatic : +A single STATIC.

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:AddStaticsByName(AddStaticNames) + +
    +
    + +

    Add STATIC(s) to SET_STATIC.

    + +

    Parameter

    +
      +
    • + +

      #string AddStaticNames : +A single name or an array of STATIC names.

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterCategories(Categories) + +
    +
    + +

    Builds a set of units out of categories.

    + + +

    Possible current categories are plane, helicopter, ground, ship.

    + +

    Parameter

    +
      +
    • + +

      #string Categories : +Can take the following values: "plane", "helicopter", "ground", "ship".

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterCoalitions(Coalitions) + +
    +
    + +

    Builds a set of units of coalitions.

    + + +

    Possible current coalitions are red, blue and neutral.

    + +

    Parameter

    +
      +
    • + +

      #string Coalitions : +Can take the following values: "red", "blue", "neutral".

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterCountries(Countries) + +
    +
    + +

    Builds a set of units of defined countries.

    + + +

    Possible current countries are those known within DCS world.

    + +

    Parameter

    +
      +
    • + +

      #string Countries : +Can take those country strings known within DCS world.

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterPrefixes(Prefixes) + +
    +
    + +

    Builds a set of units of defined unit prefixes.

    + + +

    All the units starting with the given prefixes will be included within the set.

    + +

    Parameter

    +
      +
    • + +

      #string Prefixes : +The prefix of which the unit name starts with.

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterStart() + +
    +
    + +

    Starts the filtering.

    + +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FilterTypes(Types) + +
    +
    + +

    Builds a set of units of defined unit types.

    + + +

    Possible current types are those types known within DCS world.

    + +

    Parameter

    +
      +
    • + +

      #string Types : +Can take those type strings known within DCS world.

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:FindInDatabase(Event) + +
    +
    + +

    Handles the Database to check on any event that Object exists in the Database.

    + + +

    This is required, because sometimes the DATABASE event gets called later than the SETBASE event or vise versa!

    + +

    Parameter

    + +

    Return values

    +
      +
    1. + +

      #string: +The name of the STATIC

      + +
    2. +
    3. + +

      #table: +The STATIC

      + +
    4. +
    +
    +
    +
    +
    + + +SET_STATIC:FindStatic(StaticName) + +
    +
    + +

    Finds a Static based on the Static Name.

    + +

    Parameter

    +
      +
    • + +

      #string StaticName :

      + +
    • +
    +

    Return value

    + +

    Wrapper.Static#STATIC: +The found Static.

    + +
    +
    +
    +
    + + +SET_STATIC:ForEachStatic(IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_STATIC and call an interator function for each alive STATIC, providing the STATIC and optional parameters.

    + +

    Parameters

    +
      +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject, IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_STATIC and call an iterator function for each alive STATIC presence completely in a Zone, providing the STATIC and optional parameters to the called function.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:ForEachStaticInZone(IteratorFunction, ...) + +
    +
    + +

    Check if minimal one element of the SET_STATIC is in the Zone.

    + +

    Parameters

    +
      +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:ForEachStaticNotInZone(ZoneObject, IteratorFunction, ...) + +
    +
    + +

    Iterate the SET_STATIC and call an iterator function for each alive STATIC presence not in a Zone, providing the STATIC and optional parameters to the called function.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:GetCoordinate() + +
    +
    + +

    Get the center coordinate of the SET_STATIC.

    + +

    Return value

    + +

    Core.Point#COORDINATE: +The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.

    + +
    +
    +
    +
    + + +SET_STATIC:GetFirst() + +
    +
    + +

    Get the first unit from the set.

    + +

    Return value

    + +

    Wrapper.Static#STATIC: +The STATIC object.

    + +
    +
    +
    +
    + + +SET_STATIC:GetHeading() + +
    +
    + +

    Get the average heading of the SET_STATIC.

    + +

    Return value

    + +

    #number: +Heading Heading in degrees and speed in mps in case of moving units.

    + +
    +
    +
    +
    + + +SET_STATIC:GetStaticTypes() + +
    +
    + +

    Returns map of unit types.

    + +

    Return value

    + +

    #map:

    +

    string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found.

    + +
    +
    +
    +
    + + +SET_STATIC:GetStaticTypesText() + +
    +
    + +

    Returns a comma separated string of the unit types with a count in the Set.

    + +

    Return value

    + +

    #string: +The unit types string

    + +
    +
    +
    +
    + + +SET_STATIC:GetTypeNames(Delimiter) + +
    +
    + +

    Retrieve the type names of the Statics in the SET, delimited by an optional delimiter.

    + +

    Parameter

    +
      +
    • + +

      #string Delimiter : +(optional) The delimiter, which is default a comma.

      + +
    • +
    +

    Return value

    + +

    #string: +The types of the Statics delimited.

    + +
    +
    +
    +
    + + +SET_STATIC:GetVelocity() + +
    +
    + +

    Get the maximum velocity of the SET_STATIC.

    + +

    Return value

    + +

    #number: +The speed in mps in case of moving units.

    + +
    +
    +
    +
    + + +SET_STATIC:IsIncludeObject(MStatic) + +
    +
    + + + +

    Parameter

    + +

    Return value

    + +

    #SET_STATIC: +self

    + +
    +
    +
    +
    + + +SET_STATIC:IsNotInZone(ZoneObject, Zone) + +
    +
    + +

    Check if no element of the SET_STATIC is in the Zone.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      Zone :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +SET_STATIC:IsPatriallyInZone(Zone) + +
    +
    + +

    Check if minimal one element of the SET_STATIC is in the Zone.

    + +

    Parameter

    + +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +SET_STATIC:New() + +
    +
    + +

    Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.

    + +

    Return value

    + +

    #SET_STATIC:

    + + +

    Usage:

    +
    -- Define a new SET_STATIC Object. This DBObject will contain a reference to all alive Statics.
    +DBObject = SET_STATIC:New()
    + +
    +
    +
    +
    + + +SET_STATIC:RemoveStaticsByName(RemoveStaticNames) + +
    +
    + +

    Remove STATIC(s) from SET_STATIC.

    + +

    Parameter

    + +

    Return value

    + +

    self

    @@ -4537,6 +6286,11 @@ self

    Calculate the maxium A2G threat level of the SET_UNIT.

    +

    Return value

    + +

    #number: +The maximum threatlevel

    +
    @@ -4893,6 +6647,38 @@ self

    + +SET_UNIT:ForEachUnitInZone(IteratorFunction, ...) + +
    +
    + +

    Check if minimal one element of the SET_UNIT is in the Zone.

    + +

    Parameters

    +
      +
    • + +

      #function IteratorFunction : +The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.

      + +
    • +
    • + +

      ... :

      + +
    • +
    +

    Return value

    + +

    #SET_UNIT: +self

    + +
    +
    +
    +
    + SET_UNIT:ForEachUnitNotInZone(ZoneObject, IteratorFunction, ...) @@ -4986,6 +6772,24 @@ self

    + +SET_UNIT:GetCoordinate() + +
    +
    + +

    Get the center coordinate of the SET_UNIT.

    + +

    Return value

    + +

    Core.Point#COORDINATE: +The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.

    + +
    +
    +
    +
    + SET_UNIT:GetFirst() @@ -5004,6 +6808,24 @@ The UNIT object.

    + +SET_UNIT:GetHeading() + +
    +
    + +

    Get the average heading of the SET_UNIT.

    + +

    Return value

    + +

    #number: +Heading Heading in degrees and speed in mps in case of moving units.

    + +
    +
    +
    +
    + SET_UNIT:GetTypeNames(Delimiter) @@ -5085,6 +6907,24 @@ The unit types string

    + +SET_UNIT:GetVelocity() + +
    +
    + +

    Get the maximum velocity of the SET_UNIT.

    + +

    Return value

    + +

    #number: +The speed in mps in case of moving units.

    + +
    +
    +
    +
    + SET_UNIT:HasFriendlyUnits(FriendlyCoalition) @@ -5194,6 +7034,65 @@ The amount of SEADable units in the Set

    #SET_UNIT: self

    + +
    +
    +
    + + +SET_UNIT:IsNotInZone(ZoneObject, Zone) + +
    +
    + +

    Check if no element of the SET_UNIT is in the Zone.

    + +

    Parameters

    +
      +
    • + +

      Core.Zone#ZONE ZoneObject : +The Zone to be tested for.

      + +
    • +
    • + +

      Zone :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +SET_UNIT:IsPartiallyInZone(ZoneTest) + +
    +
    + +

    Check if minimal one element of the SET_UNIT is in the Zone.

    + +

    Parameter

    + +

    Return value

    + +

    #boolean:

    + +
    diff --git a/docs/Documentation/Settings.html b/docs/Documentation/Settings.html index 4dce02244..7558ad0f0 100644 --- a/docs/Documentation/Settings.html +++ b/docs/Documentation/Settings.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -148,7 +159,7 @@

    Type SETTINGS

    - + @@ -160,7 +171,7 @@ - + @@ -172,21 +183,9 @@ - - - - - + - - - - @@ -196,7 +195,13 @@ - + + + + + @@ -208,15 +213,39 @@ - + + + + + + + + + + + + + - + + + + + @@ -268,13 +297,7 @@ - - - - - + @@ -292,31 +315,37 @@ - + - + - + - + - + + + + + @@ -325,12 +354,6 @@ - - - - @@ -352,7 +375,7 @@ - + @@ -364,15 +387,39 @@ - + + + + + + + + + + + + + - + + + + + @@ -391,18 +438,18 @@ - - - - + + + + @@ -418,13 +465,7 @@ - - - - - + @@ -455,17 +496,27 @@
    -SETTINGS:A2AMenuSystem(A2ASystem) +SETTINGS:A2AMenuSystem(MenuGroup, RootMenu, A2ASystem)
    -

    Parameter

    +

    Parameters

    @@ -318,6 +333,18 @@ and any spaces before and after the resulting name are removed.

    + + + + + + + + @@ -423,9 +450,9 @@ and any spaces before and after the resulting name are removed.

    - + @@ -447,19 +474,19 @@ and any spaces before and after the resulting name are removed.

    - + - + - + @@ -507,7 +534,7 @@ and any spaces before and after the resulting name are removed.

    - + @@ -822,16 +849,6 @@ and any spaces before and after the resulting name are removed.

    - -
    SETTINGS:A2AMenuSystem(A2ASystem)SETTINGS:A2AMenuSystem(MenuGroup, RootMenu, A2ASystem)
    SETTINGS:A2GMenuSystem(A2GSystem)SETTINGS:A2GMenuSystem(MenuGroup, RootMenu, A2GSystem)
    SETTINGS.DefaultMenu - -
    SETTINGS:GetLL_Accuracy()SETTINGS:GetLL_DDM_Accuracy()

    Gets the SETTINGS LL accuracy.

    -
    SETTINGS:GetLL_DMS() -

    Gets the SETTINGS LL DMS.

    SETTINGS:IsA2A_BRA()SETTINGS:GetMessageTime(MessageType) +

    Gets the SETTINGS Message Display Timing of a MessageType

    +
    SETTINGS:IsA2A_BRAA()

    Is BRA

    SETTINGS:IsA2G_BRA()SETTINGS:IsA2A_LL_DDM() +

    Is LL DDM

    +
    SETTINGS:IsA2A_LL_DMS() +

    Is LL DMS

    +
    SETTINGS:IsA2A_MGRS() +

    Is MGRS

    +
    SETTINGS:IsA2G_BR()

    Is BRA

    SETTINGS:IsA2G_LL()SETTINGS:IsA2G_LL_DDM() -

    Is LL

    +

    Is LL DDM

    +
    SETTINGS:IsA2G_LL_DMS() +

    Is LL DMS

    SETTINGS:MenuGroupLL_AccuracySystem(PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy) - -
    SETTINGS:MenuGroupLL_DMSSystem(PlayerUnit, PlayerGroup, PlayerName, LL_DMS)SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy)
    SETTINGS:MenuLL_Accuracy(LL_Accuracy)SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime)
    SETTINGS:MenuLL_DMS(LL_DMS)SETTINGS:MenuLL_DDM_Accuracy(MenuGroup, RootMenu, LL_Accuracy)
    SETTINGS:MenuMGRS_Accuracy(MGRS_Accuracy)SETTINGS:MenuMGRS_Accuracy(MenuGroup, RootMenu, MGRS_Accuracy)
    SETTINGS:MenuMWSystem(MW)SETTINGS:MenuMWSystem(MenuGroup, RootMenu, MW)
    SETTINGS.MenuTextSETTINGS:MenuMessageTimingsSystem(MenuGroup, RootMenu, MessageType, MessageTime) + +
    SETTINGS.MessageTypeTimings SETTINGS.Metric -
    SETTINGS.Metrics -
    SETTINGS:SetA2A_BRA()SETTINGS:SetA2A_BRAA()

    Sets A2A BRA

    SETTINGS:SetA2G_BRA()SETTINGS:SetA2A_LL_DDM() +

    Sets A2A LL DDM

    +
    SETTINGS:SetA2A_LL_DMS() +

    Sets A2A LL DMS

    +
    SETTINGS:SetA2A_MGRS() +

    Sets A2A MGRS

    +
    SETTINGS:SetA2G_BR()

    Sets A2G BRA

    SETTINGS:SetA2G_LL()SETTINGS:SetA2G_LL_DDM() -

    Sets A2G LL

    +

    Sets A2G LL DDM

    +
    SETTINGS:SetA2G_LL_DMS() +

    Sets A2G LL DMS

    SETTINGS:SetLL_Accuracy(LL_Accuracy)

    Sets the SETTINGS LL accuracy.

    -
    SETTINGS:SetLL_DMS(LL_DMS) -

    Sets the SETTINGS LL DMS.

    SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy)

    Sets the SETTINGS MGRS accuracy.

    +
    SETTINGS:SetMessageTime(MessageType, MessageTime) +

    Sets the SETTINGS Message Display Timing of a MessageType

    SETTINGS:SetSystemMenu(RootMenu, MenuText) - -
    SETTINGS.SettingsMenuSETTINGS:SetSystemMenu(MenuGroup, RootMenu)

    SPAWN class, extends Base#BASE

    +

    -- Banner Image

    + +
    +

    The SPAWN class allows to spawn dynamically new groups.

    SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable)

    This method is rather complicated to understand.

    +
    SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes) +

    Randomize templates to be used as the unit representatives for the Spawned group, defined by specifying the prefix names.

    +
    SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet) +

    Randomize templates to be used as the unit representatives for the Spawned group, defined using a SET_GROUP object.

    SPAWN:SpawnAtAirbase(Airbase, Takeoff)SPAWN:SpawnAtAirbase(SpawnAirbase, Takeoff, TakeoffAltitude) -

    Will spawn a group at an airbase.

    +

    Will spawn a group at an Airbase.

    SPAWN:SpawnFromStatic(HostStatic, SpawnIndex)SPAWN:SpawnFromStatic(HostStatic, MinHeight, MaxHeight, SpawnIndex)

    Will spawn a group from a hosting static.

    SPAWN:SpawnFromUnit(HostUnit, SpawnIndex)SPAWN:SpawnFromUnit(HostUnit, MinHeight, MaxHeight, SpawnIndex)

    Will spawn a group from a hosting unit.

    SPAWN:SpawnFromVec2(Vec2, SpawnIndex)SPAWN:SpawnFromVec2(Vec2, MinHeight, MaxHeight, SpawnIndex)

    Will spawn a group from a Vec2 in 3D space.

    SPAWN:SpawnInZone(Zone, RandomizeGroup, SpawnIndex)SPAWN:SpawnInZone(Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex)

    Will spawn a Group within a given Zone.

    SPAWN:_TranslateRotate(SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle) -
    - -

    Type SPAWN.Takeoff

    - - - -
    SPAWN.Takeoff.type -
    @@ -849,10 +866,14 @@ and any spaces before and after the resulting name are removed.

    SPAWN class, extends Base#BASE

    +

    -- Banner Image

    + +
    +

    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 @@ -1027,6 +1048,7 @@ So in principle, the group list will contain all parameters and configurations a

  • 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. @@ -1825,6 +1847,99 @@ Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150

    + +SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes) + +
    +
    + +

    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.

    + +

    Parameter

    +
      +
    • + +

      #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 value

    + +

    #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 )
    + +
    +
    +
    +
    + + +SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet) + +
    +
    + +

    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.

    + +

    Parameter

    +
      +
    • + +

      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 value

    + +

    #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 )
    + +
    +
    +
    +
    + SPAWN:InitRandomizeUnits(RandomizeUnits, OuterRadius, InnerRadius) @@ -2194,6 +2309,9 @@ The group that was spawned. You can use this group for further actions.

    + +

    Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.

    +
    @@ -2263,23 +2381,41 @@ The group that was spawned. You can use this group for further actions.

    -SPAWN:SpawnAtAirbase(Airbase, Takeoff) +SPAWN:SpawnAtAirbase(SpawnAirbase, Takeoff, TakeoffAltitude)
    -

    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:

    + + + +

    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!

    + +

    Parameters

    Return values

    @@ -2305,6 +2447,24 @@ Nothing was spawned.

    +

    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 )
    +
    +
    @@ -2356,7 +2516,7 @@ Nothing was spawned.

    -SPAWN:SpawnFromStatic(HostStatic, SpawnIndex) +SPAWN:SpawnFromStatic(HostStatic, MinHeight, MaxHeight, SpawnIndex)
    @@ -2377,6 +2537,18 @@ The static dropping or unloading the group.

  • +

    #number MinHeight : +(optional) The minimum height to spawn an airborne group into the zone.

    + +
  • +
  • + +

    #number MaxHeight : +(optional) The maximum height to spawn an airborne group into the zone.

    + +
  • +
  • +

    #number SpawnIndex : (optional) The index which group to spawn within the given zone.

    @@ -2397,13 +2569,24 @@ Nothing was spawned.

  • +

    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 )  
    +
    +
    -SPAWN:SpawnFromUnit(HostUnit, SpawnIndex) +SPAWN:SpawnFromUnit(HostUnit, MinHeight, MaxHeight, SpawnIndex)
    @@ -2425,6 +2608,18 @@ The air or ground unit dropping or unloading the group.

  • +

    #number MinHeight : +(optional) The minimum height to spawn an airborne group into the zone.

    + +
  • +
  • + +

    #number MaxHeight : +(optional) The maximum height to spawn an airborne group into the zone.

    + +
  • +
  • +

    #number SpawnIndex : (optional) The index which group to spawn within the given zone.

    @@ -2445,13 +2640,24 @@ Nothing was spawned.

  • +

    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 )  
    +
    +
    -SPAWN:SpawnFromVec2(Vec2, SpawnIndex) +SPAWN:SpawnFromVec2(Vec2, MinHeight, MaxHeight, SpawnIndex)
    @@ -2473,6 +2679,18 @@ The Vec2 coordinates where to spawn the group.

  • +

    #number MinHeight : +(optional) The minimum height to spawn an airborne group into the zone.

    + +
  • +
  • + +

    #number MaxHeight : +(optional) The maximum height to spawn an airborne group into the zone.

    + +
  • +
  • +

    #number SpawnIndex : (optional) The index which group to spawn within the given zone.

    @@ -2493,6 +2711,17 @@ Nothing was spawned.

  • +

    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 )  
    +
    +
    @@ -2650,7 +2879,7 @@ SpawnGroupName

    -SPAWN:SpawnInZone(Zone, RandomizeGroup, SpawnIndex) +SPAWN:SpawnInZone(Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex)
    @@ -2678,6 +2907,18 @@ The zone where the group is to be spawned.

  • +

    #number MinHeight : +(optional) The minimum height to spawn an airborne group into the zone.

    + +
  • +
  • + +

    #number MaxHeight : +(optional) The maximum height to spawn an airborne group into the zone.

    + +
  • +
  • +

    #number SpawnIndex : (optional) The index which group to spawn within the given zone.

    @@ -2698,6 +2939,26 @@ when nothing was spawned.

  • +

    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 )  
    +  
    +
    @@ -2743,6 +3004,9 @@ when nothing was spawned.

    + +

    By default, no InitLimit

    +
    @@ -2778,7 +3042,7 @@ when nothing was spawned.

    - + #number SPAWN.SpawnMaxGroups @@ -2795,7 +3059,7 @@ when nothing was spawned.

    - + #number SPAWN.SpawnMaxUnitsAlive @@ -3123,7 +3387,7 @@ Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
    - + #boolean SPAWN.SpawnUnControlled @@ -3736,20 +4000,6 @@ True = Continue Scheduler

    Enumerator for spawns at airbases

    -

    Field(s)

    -
    -
    - - -SPAWN.Takeoff.type - -
    -
    - - - -
    -
    diff --git a/docs/Documentation/SpawnStatic.html b/docs/Documentation/SpawnStatic.html index d8aa5e633..b9cd35a99 100644 --- a/docs/Documentation/SpawnStatic.html +++ b/docs/Documentation/SpawnStatic.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -168,6 +179,12 @@ SPAWNSTATIC:NewFromType(SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID)

    Creates the main object to spawn a Static based on a type name.

    + + + + SPAWNSTATIC:Spawn(Heading, (, NewName) + +

    Creates a new Static at the original position.

    @@ -343,6 +360,44 @@ is the name of the type.

    #SPAWNSTATIC:

    + +
    +
    +
    + + +SPAWNSTATIC:Spawn(Heading, (, NewName) + +
    +
    + +

    Creates a new Static at the original position.

    + +

    Parameters

    +
      +
    • + +

      #number Heading : +The heading of the static, which is a number in degrees from 0 to 360.

      + +
    • +
    • + +

      #string ( : +ptional) The name of the new static.

      + +
    • +
    • + +

      NewName :

      + +
    • +
    +

    Return value

    + +

    #SPAWNSTATIC:

    + +
    diff --git a/docs/Documentation/Spot.html b/docs/Documentation/Spot.html index 5fdc3b305..ff1b042ec 100644 --- a/docs/Documentation/Spot.html +++ b/docs/Documentation/Spot.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Static.html b/docs/Documentation/Static.html index efa72665c..c7a8dc808 100644 --- a/docs/Documentation/Static.html +++ b/docs/Documentation/Static.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -147,6 +158,12 @@ STATIC:GetThreatLevel() + + + + STATIC:ReSpawn(Coordinate, Heading) + +

    Respawn the Unit using a (tweaked) template of the parent Group.

    @@ -275,6 +292,34 @@ Raise an error if not found.

    + +
    +
    +
    + + +STATIC:ReSpawn(Coordinate, Heading) + +
    +
    + +

    Respawn the Unit using a (tweaked) template of the parent Group.

    + +

    Parameters

    +
      +
    • + +

      Core.Point#COORDINATE Coordinate : +The coordinate where to spawn the new Static.

      + +
    • +
    • + +

      #number Heading : +The heading of the unit respawn.

      + +
    • +
    @@ -313,6 +358,8 @@ Raise an error if not found.

    +

    Type UNIT

    + diff --git a/docs/Documentation/StaticObject.html b/docs/Documentation/StaticObject.html index 6ff504920..0638c4e5a 100644 --- a/docs/Documentation/StaticObject.html +++ b/docs/Documentation/StaticObject.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Task.html b/docs/Documentation/Task.html index 1cf449b57..e17fc7c8d 100644 --- a/docs/Documentation/Task.html +++ b/docs/Documentation/Task.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -183,6 +194,18 @@ TASK:CrashGroup(PlayerUnit, PlayerGroup)

    A PlayerUnit crashed in a Task.

    + + + + TASK.DetectedItemIndex + + + + + + TASK.Detection + + @@ -225,6 +248,12 @@ TASK:GetID()

    Gets the ID of the Task

    + + + + TASK:GetInfo(TaskInfo) + +

    Gets the Information of the Task

    @@ -249,6 +278,12 @@ TASK:GetPlayerNames()

    Create a list of the players in the Task.

    + + + + TASK:GetPlayerProgress(PlayerName) + + @@ -306,7 +341,7 @@ - TASK:Goal() + TASK:Goal(PlayerUnit, PlayerName)

    Goal Trigger for TASK

    @@ -390,7 +425,25 @@ - TASK.MenuAssignToGroup(MenuParam) + TASK:MenuAssignToGroup(TaskGroup) + + + + + + TASK.MenuAssigned + + + + + + TASK:MenuMarkToGroup(TaskGroup) + + + + + + TASK.MenuPlanned @@ -426,7 +479,7 @@ - TASK:OnAfterGoal(Controllable, From, Event, To) + TASK:OnAfterGoal(From, Event, To, PlayerUnit, PlayerName)

    Goal Handler OnAfter for TASK

    @@ -450,7 +503,7 @@ - TASK:OnBeforeGoal(Controllable, From, Event, To) + TASK:OnBeforeGoal(From, Event, To, PlayerUnit, PlayerName)

    Goal Handler OnBefore for TASK

    @@ -498,7 +551,7 @@ - TASK:ReportSummary() + TASK:ReportSummary(ReportGroup)

    Create a summary report of the Task.

    @@ -519,6 +572,12 @@ TASK:SetBriefing(TaskBriefing)

    Sets a Task briefing.

    + + + + TASK:SetDetection(Detection, DetectedItemIndex) + +

    Set detection of a task

    @@ -546,7 +605,7 @@ - TASK:SetInfo(TaskInfo, TaskInfoText) + TASK:SetInfo(TaskInfo, TaskInfoText, TaskInfoOrder)

    Sets the Information on the Task

    @@ -681,6 +740,12 @@ TASK.TaskID + + + + TASK.TaskInfo + + @@ -750,7 +815,7 @@ - TASK:__Goal(Delay) + TASK:__Goal(Delay, PlayerUnit, PlayerName)

    Goal Asynchronous Trigger for TASK

    @@ -789,6 +854,12 @@ TASK:onenterAssigned(Event, From, To, PlayerUnit, PlayerName)

    FSM function for a TASK

    + + + + TASK:onenterCancelled(From, Event, To) + +

    FSM function for a TASK

    @@ -1126,6 +1197,34 @@ The CLIENT or UNIT of the Player aborting the Task.

    #TASK:

    + +
    +
    +
    + + + +TASK.DetectedItemIndex + +
    +
    + + + +
    +
    +
    +
    + + + +TASK.Detection + +
    +
    + + +
    @@ -1257,6 +1356,33 @@ TaskID

    + +TASK:GetInfo(TaskInfo) + +
    +
    + +

    Gets the Information of the Task

    + +

    Parameter

    +
      +
    • + +

      #string TaskInfo : +The key and title of the task information.

      + +
    • +
    +

    Return value

    + +

    #string: +TaskInfoText The Task info text.

    + +
    +
    +
    +
    + TASK:GetMission() @@ -1329,6 +1455,27 @@ The total number of players in the task.

    + +TASK:GetPlayerProgress(PlayerName) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      PlayerName :

      + +
    • +
    +
    +
    +
    +
    + TASK:GetProcessTemplate(ProcessName) @@ -1511,13 +1658,28 @@ TaskType

    -TASK:Goal() +TASK:Goal(PlayerUnit, PlayerName)

    Goal Trigger for TASK

    +

    Parameters

    +
      +
    • + +

      Wrapper.Unit#UNIT PlayerUnit : +The Unit of the player.

      + +
    • +
    • + +

      #string PlayerName : +The name of the player.

      + +
    • +
    @@ -1762,7 +1924,7 @@ true if Unit is part of the Task.

    -TASK.MenuAssignToGroup(MenuParam) +TASK:MenuAssignToGroup(TaskGroup)
    @@ -1773,10 +1935,57 @@ true if Unit is part of the Task.

    +
    +
    +
    +
    + + +TASK.MenuAssigned + +
    +
    + + + +
    +
    +
    +
    + + +TASK:MenuMarkToGroup(TaskGroup) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + +TASK.MenuPlanned + +
    +
    + + +
    @@ -1913,7 +2122,7 @@ self

    -TASK:OnAfterGoal(Controllable, From, Event, To) +TASK:OnAfterGoal(From, Event, To, PlayerUnit, PlayerName)
    @@ -1924,11 +2133,6 @@ self

    @@ -2033,7 +2249,7 @@ The name of the Player.

    -TASK:OnBeforeGoal(Controllable, From, Event, To) +TASK:OnBeforeGoal(From, Event, To, PlayerUnit, PlayerName)
    @@ -2044,11 +2260,6 @@ The name of the Player.

    Return value

    @@ -2267,7 +2490,7 @@ self

    -TASK:ReportSummary() +TASK:ReportSummary(ReportGroup)
    @@ -2277,6 +2500,14 @@ self

    List the Task Name and Status

    +

    Parameter

    +

    Return value

    #string:

    @@ -2352,6 +2583,37 @@ self

    #TASK: self

    +
    +
    +
    +
    + + +TASK:SetDetection(Detection, DetectedItemIndex) + +
    +
    + +

    Set detection of a task

    + +

    Parameters

    + +

    Return value

    + +

    #TASK:

    + +
    @@ -2445,7 +2707,7 @@ self

    -TASK:SetInfo(TaskInfo, TaskInfoText) +TASK:SetInfo(TaskInfo, TaskInfoText, TaskInfoOrder)
    @@ -2456,12 +2718,20 @@ self

    • -

      #string TaskInfo :

      +

      #string TaskInfo : +The key and title of the task information.

    • -

      TaskInfoText :

      +

      #string TaskInfoText : +The Task info text.

      + +
    • +
    • + +

      #number TaskInfoOrder : +The ordering, a number between 0 and 99.

    @@ -2951,6 +3221,19 @@ Fsm#FSM_PROCESS

    +
    +
    +
    +
    + + +TASK.TaskInfo + +
    +
    + + +
    @@ -3037,7 +3320,7 @@ Fsm#FSM_PROCESS

    @@ -3134,19 +3417,31 @@ self

    -TASK:__Goal(Delay) +TASK:__Goal(Delay, PlayerUnit, PlayerName)

    Goal Asynchronous Trigger for TASK

    -

    Parameter

    +

    Parameters

    • #number Delay :

      +
    • +
    • + +

      Wrapper.Unit#UNIT PlayerUnit : +The Unit of the player.

      + +
    • +
    • + +

      #string PlayerName : +The name of the player.

      +
    @@ -3320,6 +3615,37 @@ self

    + +TASK:onenterCancelled(From, Event, To) + +
    +
    + +

    FSM function for a TASK

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + TASK:onenterFailed(From, Event, To) diff --git a/docs/Documentation/TaskZoneCapture.html b/docs/Documentation/TaskZoneCapture.html new file mode 100644 index 000000000..684cce1e9 --- /dev/null +++ b/docs/Documentation/TaskZoneCapture.html @@ -0,0 +1,800 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module TaskZoneCapture

    + +

    Tasking - The TASK_Protect models tasks for players to protect or capture specific zones.

    + + + +
    + +

    Author: Sven Van de Velde (FlightControl)

    + +

    Contributions: MillerTime

    + +
    +

    + +

    Global(s)

    + + + + + + + + + +
    TASK_ZONE_CAPTURE +

    TASKZONECAPTURE class, extends TaskZoneGoal#TASKZONEGOAL

    + +

    The TASKZONECAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.

    +
    TASK_ZONE_GOAL +

    TASKZONEGOAL class, extends Task#TASK

    + +

    The TASKZONEGOAL class defines the task to protect or capture a protection zone.

    +
    +

    Type TASK_ZONE_CAPTURE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK_ZONE_CAPTURE:New(Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing) +

    Instantiates a new TASKZONECAPTURE.

    +
    TASK_ZONE_CAPTURE:OnAfterGoal(TaskUnit, From, Event, To, PlayerUnit, PlayerName) + +
    TASK_ZONE_CAPTURE:ReportOrder(ReportGroup) + +
    TASK_ZONE_CAPTURE.TaskCoalition + +
    TASK_ZONE_CAPTURE.TaskCoalitionName + +
    TASK_ZONE_CAPTURE.TaskZoneName + +
    TASK_ZONE_CAPTURE:UpdateTaskInfo() +

    Instantiates a new TASKZONECAPTURE.

    +
    TASK_ZONE_CAPTURE.ZoneGoal + +
    + +

    Type TASK_ZONE_GOAL

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK_ZONE_GOAL:GetGoalTotal() + +
    TASK_ZONE_GOAL:GetMarkInfo(TaskInfoID, TaskInfo) + +
    TASK_ZONE_GOAL:GetPlannedMenuText() + +
    TASK_ZONE_GOAL:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) + +
    TASK_ZONE_GOAL:GetTargetZone(TaskUnit) + +
    TASK_ZONE_GOAL:New(Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing) +

    Instantiates a new TASKZONEGOAL.

    +
    TASK_ZONE_GOAL:SetGoalTotal(GoalTotal) + +
    TASK_ZONE_GOAL:SetProtect(ZoneGoal) + +
    TASK_ZONE_GOAL:SetTargetZone(TargetZone, TaskUnit) + +
    TASK_ZONE_GOAL.TaskType + +
    TASK_ZONE_GOAL.ZoneGoal + +
    + +

    Global(s)

    +
    +
    + + #TASK_ZONE_CAPTURE + +TASK_ZONE_CAPTURE + +
    +
    + +

    TASKZONECAPTURE class, extends TaskZoneGoal#TASKZONEGOAL

    + +

    The TASKZONECAPTURE 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 TASKZONECAPTURE is used by the TaskA2GDispatcher#TASKA2GDISPATCHER to automatically create SEAD tasks +based on detected enemy ground targets.

    + + +
    +
    +
    +
    + + #TASK_ZONE_GOAL + +TASK_ZONE_GOAL + +
    +
    + +

    TASKZONEGOAL class, extends Task#TASK

    + +

    The TASKZONEGOAL class defines the task to protect or capture a protection zone.

    + + +

    The TASKZONEGOAL 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:

    + + + + +
    +
    +

    Type TaskZoneCapture

    + +

    Type FSM_PROCESS

    + +

    Type TASK_ZONE_CAPTURE

    + +

    The TASKZONECAPTURE class

    + +

    Field(s)

    +
    +
    + + +TASK_ZONE_CAPTURE:New(Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing) + +
    +
    + +

    Instantiates a new TASKZONECAPTURE.

    + +

    Parameters

    + +

    Return value

    + +

    #TASKZONECAPTURE: +self

    + +
    +
    +
    +
    + + +TASK_ZONE_CAPTURE:OnAfterGoal(TaskUnit, From, Event, To, PlayerUnit, PlayerName) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      Wrapper.Unit#UNIT TaskUnit :

      + +
    • +
    • + +

      From :

      + +
    • +
    • + +

      Event :

      + +
    • +
    • + +

      To :

      + +
    • +
    • + +

      PlayerUnit :

      + +
    • +
    • + +

      PlayerName :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_ZONE_CAPTURE:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + + + +TASK_ZONE_CAPTURE.TaskCoalition + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_ZONE_CAPTURE.TaskCoalitionName + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_ZONE_CAPTURE.TaskZoneName + +
    +
    + + + +
    +
    +
    +
    + + +TASK_ZONE_CAPTURE:UpdateTaskInfo() + +
    +
    + +

    Instantiates a new TASKZONECAPTURE.

    + +
    +
    +
    +
    + + Core.ZoneGoalCoalition#ZONE_GOAL_COALITION + +TASK_ZONE_CAPTURE.ZoneGoal + +
    +
    + + + +
    +
    + +

    Type TASK_ZONE_GOAL

    + +

    The TASKZONEGOAL class

    + +

    Field(s)

    +
    +
    + + +TASK_ZONE_GOAL:GetGoalTotal() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_ZONE_GOAL:GetMarkInfo(TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_ZONE_GOAL:GetPlannedMenuText() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_ZONE_GOAL:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_ZONE_GOAL:GetTargetZone(TaskUnit) + +
    +
    + + + +

    Parameter

    + +

    Return value

    + +

    Core.Zone#ZONE_BASE: +The Zone object where the Target is located on the map.

    + +
    +
    +
    +
    + + +TASK_ZONE_GOAL:New(Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing) + +
    +
    + +

    Instantiates a new TASKZONEGOAL.

    + +

    Parameters

    + +

    Return value

    + +

    #TASKZONEGOAL: +self

    + +
    +
    +
    +
    + + +TASK_ZONE_GOAL:SetGoalTotal(GoalTotal) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      GoalTotal :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_ZONE_GOAL:SetProtect(ZoneGoal) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + +TASK_ZONE_GOAL:SetTargetZone(TargetZone, TaskUnit) + +
    +
    + + + +

    Parameters

    + +
    +
    +
    +
    + + + +TASK_ZONE_GOAL.TaskType + +
    +
    + + + +
    +
    +
    +
    + + Core.ZoneGoal#ZONE_GOAL + +TASK_ZONE_GOAL.ZoneGoal + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/Task_A2A.html b/docs/Documentation/Task_A2A.html index 584ef585f..03568dc9f 100644 --- a/docs/Documentation/Task_A2A.html +++ b/docs/Documentation/Task_A2A.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -159,6 +170,18 @@ based on the tasking capabilities defined in Task#TA

    Type TASK_A2A

    + + + + + + + + + + + + @@ -192,6 +221,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -232,6 +267,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -256,6 +297,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -272,6 +319,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -296,6 +349,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -312,6 +371,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -336,6 +401,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -478,6 +549,45 @@ The task is given a name and a briefing, that is used in the menu structure and
    + +TASK_A2A:GetGoalTotal() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_A2A:GetMarkInfo(TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2A:GetPlannedMenuText() @@ -553,6 +663,37 @@ The Zone object where the RendezVous is located on the map.

    + +TASK_A2A:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2A:GetTargetCoordinate(TaskUnit) @@ -671,6 +812,19 @@ If the TargetZone parameter is specified, the player will be routed to the cente

    #TASK_A2A: self

    + +
    +
    +
    + + +TASK_A2A:SetGoalTotal() + +
    +
    + + +
    @@ -869,6 +1023,27 @@ self

    + +TASK_A2A_ENGAGE:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName, Penalty, TaskUnit) @@ -992,6 +1167,19 @@ The score in points.

    + +
    +
    +
    + + +TASK_A2A_ENGAGE:UpdateTaskInfo() + +
    +
    + + +
    @@ -1088,6 +1276,27 @@ The briefing of the task.

    + +TASK_A2A_INTERCEPT:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName, Penalty, TaskUnit) @@ -1211,6 +1420,19 @@ The score in points.

    + +
    +
    +
    + + +TASK_A2A_INTERCEPT:UpdateTaskInfo() + +
    +
    + + +
    @@ -1307,6 +1529,27 @@ self

    + +TASK_A2A_SWEEP:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2A_SWEEP:SetScoreOnFail(PlayerName, Penalty, TaskUnit) @@ -1430,6 +1673,19 @@ The score in points.

    + +
    +
    +
    + + +TASK_A2A_SWEEP:UpdateTaskInfo() + +
    +
    + + +
    diff --git a/docs/Documentation/Task_A2A_Dispatcher.html b/docs/Documentation/Task_A2A_Dispatcher.html index e9ee5a1da..2dd3bd9ee 100644 --- a/docs/Documentation/Task_A2A_Dispatcher.html +++ b/docs/Documentation/Task_A2A_Dispatcher.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -199,6 +210,12 @@
    + + + + @@ -301,7 +318,7 @@ increasing or decreasing the radar coverage of the Early Warning System.

    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 ) @@ -760,6 +777,27 @@ Return true if you want the task assigning to continue... false will cancel the
    + +TASK_A2A_DISPATCHER:RemoveTask(TaskIndex) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      TaskIndex :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius) diff --git a/docs/Documentation/Task_A2G.html b/docs/Documentation/Task_A2G.html index 6b55b2294..ef59a9787 100644 --- a/docs/Documentation/Task_A2G.html +++ b/docs/Documentation/Task_A2G.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -159,6 +170,18 @@ based on the tasking capabilities defined in Task#TA

    Type TASK_A2G

    TASK_A2A:GetGoalTotal() + +
    TASK_A2A:GetMarkInfo(TaskInfoID, TaskInfo) + +
    TASK_A2A:GetPlannedMenuText() @@ -174,6 +197,12 @@ based on the tasking capabilities defined in Task#TA TASK_A2A:GetRendezVousZone(TaskUnit) +
    TASK_A2A:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) +
    TASK_A2A:New(Mission, SetAttack, TaskName, UnitSetTargets, TargetDistance, TargetZone, TargetSetUnit, TaskType, TaskBriefing)

    Instantiates a new TASK_A2A.

    +
    TASK_A2A:SetGoalTotal() +
    TASK_A2A_ENGAGE:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2AENGAGE.

    +
    TASK_A2A_ENGAGE:ReportOrder(ReportGroup) +
    TASK_A2A_ENGAGE.TargetSetUnit +
    TASK_A2A_ENGAGE:UpdateTaskInfo() +
    TASK_A2A_INTERCEPT:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2AINTERCEPT.

    +
    TASK_A2A_INTERCEPT:ReportOrder(ReportGroup) +
    TASK_A2A_INTERCEPT.TargetSetUnit +
    TASK_A2A_INTERCEPT:UpdateTaskInfo() +
    TASK_A2A_SWEEP:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2ASWEEP.

    +
    TASK_A2A_SWEEP:ReportOrder(ReportGroup) +
    TASK_A2A_SWEEP.TargetSetUnit +
    TASK_A2A_SWEEP:UpdateTaskInfo() +
    TASK_A2A_DISPATCHER:ProcessDetected(Detection)

    Assigns tasks in relation to the detected items to the Set#SET_GROUP.

    +
    TASK_A2A_DISPATCHER:RemoveTask(TaskIndex) +
    + + + + + + + + + + + + @@ -192,6 +221,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -210,6 +245,12 @@ based on the tasking capabilities defined in Task#TA + + + + @@ -232,30 +273,42 @@ based on the tasking capabilities defined in Task#TA + + + + + + + + @@ -272,30 +325,42 @@ based on the tasking capabilities defined in Task#TA + + + + + + + + @@ -312,30 +377,42 @@ based on the tasking capabilities defined in Task#TA + + + + + + + + @@ -468,6 +545,45 @@ based on detected enemy ground targets.

    + +TASK_A2G:GetGoalTotal() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_A2G:GetMarkInfo(TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2G:GetPlannedMenuText() @@ -543,6 +659,37 @@ The Zone object where the RendezVous is located on the map.

    + +TASK_A2G:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    • + +

      TaskInfoID :

      + +
    • +
    • + +

      TaskInfo :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2G:GetTargetCoordinate(TaskUnit) @@ -661,6 +808,19 @@ If the TargetZone parameter is specified, the player will be routed to the cente

    #TASK_A2G: self

    + +
    +
    +
    + + +TASK_A2G:SetGoalTotal() + +
    +
    + + +
    @@ -753,6 +913,28 @@ The Coordinate object where the Target is located on the map.

    + +TASK_A2G:SetTargetSetUnit(TargetSetUnit) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + TASK_A2G:SetTargetZone(TargetZone, TaskUnit) @@ -849,13 +1031,34 @@ self

    + +TASK_A2G_BAI:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2G_BAI:SetScoreOnFail(PlayerName, Penalty, TaskUnit)
    -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    Parameters

      @@ -893,7 +1096,7 @@ The penalty in points, must be a negative value!

    -

    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 .

    Parameters

      @@ -931,7 +1134,7 @@ The score in points to be granted when task process has been achieved.

    -

    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.

    Parameters

      @@ -972,6 +1175,19 @@ The score in points.

      +
    +
    +
    +
    + + +TASK_A2G_BAI:UpdateTaskInfo() + +
    +
    + + +
    @@ -1068,13 +1284,34 @@ self

    + +TASK_A2G_CAS:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2G_CAS:SetScoreOnFail(PlayerName, Penalty, TaskUnit)
    -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    Parameters

      @@ -1112,7 +1349,7 @@ The penalty in points, must be a negative value!

      -

      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 .

      Parameters

        @@ -1150,7 +1387,7 @@ The score in points to be granted when task process has been achieved.

        -

        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.

        Parameters

          @@ -1191,6 +1428,19 @@ The score in points.

          +
        +
    +
    +
    + + +TASK_A2G_CAS:UpdateTaskInfo() + +
    +
    + + +
    @@ -1287,13 +1537,34 @@ self

    + +TASK_A2G_SEAD:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_A2G_SEAD:SetScoreOnFail(PlayerName, Penalty, TaskUnit)
    -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    Parameters

      @@ -1331,7 +1602,7 @@ The penalty in points, must be a negative value!

      -

      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 .

      Parameters

        @@ -1369,7 +1640,7 @@ The score in points to be granted when task process has been achieved.

        -

        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.

        Parameters

          @@ -1410,6 +1681,19 @@ The score in points.

          +
        +
    +
    +
    + + +TASK_A2G_SEAD:UpdateTaskInfo() + +
    +
    + + +
    diff --git a/docs/Documentation/Task_A2G_Dispatcher.html b/docs/Documentation/Task_A2G_Dispatcher.html index 62948a354..f7ec79ac2 100644 --- a/docs/Documentation/Task_A2G_Dispatcher.html +++ b/docs/Documentation/Task_A2G_Dispatcher.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -183,6 +194,12 @@
    + + + + @@ -272,11 +289,21 @@ Find a summary below describing for which situation a task type is created:

    -

    Return value

    +

    Return values

    +
      +
    1. -

      Tasking.Task#TASK:

      +

      Core.Set#SET_UNIT: +TargetSetUnit: The target set of units.

      +
    2. +
    3. + +

      #nil: +If there are no targets to be set.

      +
    4. +
    @@ -298,11 +325,21 @@ Find a summary below describing for which situation a task type is created:

    -

    Return value

    +

    Return values

    +
      +
    1. -

      Tasking.Task#TASK:

      +

      Core.Set#SET_UNIT: +TargetSetUnit: The target set of units.

      +
    2. +
    3. + +

      #nil: +If there are no targets to be set.

      +
    4. +
    @@ -382,7 +419,7 @@ Find a summary below describing for which situation a task type is created:

    1. -

      Set#SET_UNIT: +

      Core.Set#SET_UNIT: TargetSetUnit: The target set of units.

    2. @@ -522,6 +559,27 @@ The detection created by the Detectio

      #boolean: Return true if you want the task assigning to continue... false will cancel the loop.

      + +
    +
    +
    + + +TASK_A2G_DISPATCHER:RemoveTask(TaskIndex) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      TaskIndex :

      + +
    • +
    diff --git a/docs/Documentation/Task_Cargo.html b/docs/Documentation/Task_Cargo.html index 8c17ced64..092a671c6 100644 --- a/docs/Documentation/Task_Cargo.html +++ b/docs/Documentation/Task_Cargo.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -177,6 +188,18 @@ and various dedicated deployment zones.

    + + + + + + + + @@ -195,6 +218,12 @@ and various dedicated deployment zones.

    + + + + @@ -231,6 +260,12 @@ and various dedicated deployment zones.

    + + + + @@ -249,6 +284,12 @@ and various dedicated deployment zones.

    + + + + @@ -343,6 +384,12 @@ and various dedicated deployment zones.

    + + + + @@ -355,6 +402,12 @@ and various dedicated deployment zones.

    + + + +
    TASK_A2G:GetGoalTotal() + +
    TASK_A2G:GetMarkInfo(TaskInfoID, TaskInfo) + +
    TASK_A2G:GetPlannedMenuText() @@ -174,6 +197,12 @@ based on the tasking capabilities defined in Task#TA TASK_A2G:GetRendezVousZone(TaskUnit) +
    TASK_A2G:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo) +
    TASK_A2G:New(Mission, SetGroup, TaskName, UnitSetTargets, TargetDistance, TargetZone, TargetSetUnit, TaskType, TaskBriefing)

    Instantiates a new TASK_A2G.

    +
    TASK_A2G:SetGoalTotal() +
    TASK_A2G:SetTargetCoordinate(TargetCoordinate, TaskUnit) +
    TASK_A2G:SetTargetSetUnit(TargetSetUnit) +
    TASK_A2G_BAI:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2GBAI.

    +
    TASK_A2G_BAI:ReportOrder(ReportGroup) +
    TASK_A2G_BAI:SetScoreOnFail(PlayerName, Penalty, TaskUnit) -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    TASK_A2G_BAI:SetScoreOnProgress(PlayerName, Score, TaskUnit) -

    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 .

    TASK_A2G_BAI:SetScoreOnSuccess(PlayerName, Score, TaskUnit) -

    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.

    TASK_A2G_BAI.TargetSetUnit +
    TASK_A2G_BAI:UpdateTaskInfo() +
    TASK_A2G_CAS:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2GCAS.

    +
    TASK_A2G_CAS:ReportOrder(ReportGroup) +
    TASK_A2G_CAS:SetScoreOnFail(PlayerName, Penalty, TaskUnit) -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    TASK_A2G_CAS:SetScoreOnProgress(PlayerName, Score, TaskUnit) -

    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 .

    TASK_A2G_CAS:SetScoreOnSuccess(PlayerName, Score, TaskUnit) -

    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.

    TASK_A2G_CAS.TargetSetUnit +
    TASK_A2G_CAS:UpdateTaskInfo() +
    TASK_A2G_SEAD:New(Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)

    Instantiates a new TASKA2GSEAD.

    +
    TASK_A2G_SEAD:ReportOrder(ReportGroup) +
    TASK_A2G_SEAD:SetScoreOnFail(PlayerName, Penalty, TaskUnit) -

    Set a penalty when the A2A attack has failed.

    +

    Set a penalty when the A2G attack has failed.

    TASK_A2G_SEAD:SetScoreOnProgress(PlayerName, Score, TaskUnit) -

    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 .

    TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName, Score, TaskUnit) -

    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.

    TASK_A2G_SEAD.TargetSetUnit +
    TASK_A2G_SEAD:UpdateTaskInfo() +
    TASK_A2G_DISPATCHER:ProcessDetected(Detection)

    Assigns tasks in relation to the detected items to the Set#SET_GROUP.

    +
    TASK_A2G_DISPATCHER:RemoveTask(TaskIndex) +
    TASK_CARGO:AddDeployZone(DeployZone, TaskUnit) +
    TASK_CARGO.CargoItemCount + +
    TASK_CARGO.CargoLimit +
    TASK_CARGO:GetDeployZones() +
    TASK_CARGO:GetGoalTotal() +
    TASK_CARGO.SetCargo +
    TASK_CARGO:SetCargoLimit(CargoLimit) +

    Set a limit on the amount of cargo items that can be loaded into the Carriers.

    TASK_CARGO:SetDeployZones(@, TaskUnit, DeployZones) +
    TASK_CARGO:SetGoalTotal() +
    TASK_CARGO_TRANSPORT:OnBeforeCargoPickedUp(From, Event, To, TaskUnit, Cargo)

    OnBefore Transition Handler for Event CargoPickedUp.

    +
    TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup) +
    TASK_CARGO_TRANSPORT:__CargoPickedUp(Delay, TaskUnit, Cargo)

    Asynchronous Event Trigger for Event CargoPickedUp.

    +
    TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit, From, Event, To) +
    @@ -510,7 +563,7 @@ based on the tasking capabilities defined in Task#TA
    - + Core.Cargo#CARGO FSM_PROCESS.Cargo @@ -524,6 +577,7 @@ based on the tasking capabilities defined in Task#TA
    + FSM_PROCESS.DeployZone @@ -566,6 +620,37 @@ based on the tasking capabilities defined in Task#TA

    #TASK_CARGO:

    + +
    +
    +
    + + + +TASK_CARGO.CargoItemCount + +
    +
    + + + + +

    Map of Carriers having a cargo item count to check the cargo loading limits.

    + +
    +
    +
    +
    + + + +TASK_CARGO.CargoLimit + +
    +
    + + +
    @@ -619,6 +704,19 @@ The Cargo Set.

    #list: Core.Zone#ZONE_BASE> The Deployment Zones.

    + +
    +
    +
    + + +TASK_CARGO:GetGoalTotal() + +
    +
    + + +
    @@ -775,6 +873,33 @@ self

    + +
    +
    +
    + + +TASK_CARGO:SetCargoLimit(CargoLimit) + +
    +
    + +

    Set a limit on the amount of cargo items that can be loaded into the Carriers.

    + +

    Parameter

    +
      +
    • + +

      CargoLimit : +Specifies a number of cargo items that can be loaded in the helicopter.

      + +
    • +
    +

    Return value

    + +

    #TASK_CARGO:

    + +
    @@ -875,6 +1000,19 @@ ist DeployZones

    #TASK_CARGO:

    + +
    +
    +
    + + +TASK_CARGO:SetGoalTotal() + +
    +
    + + +
    @@ -1410,6 +1548,27 @@ Return false to cancel Transition.

    + +TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      ReportGroup :

      + +
    • +
    +
    +
    +
    +
    + TASK_CARGO_TRANSPORT:__CargoDeployed(Delay, TaskUnit, Cargo, DeployZone) @@ -1480,6 +1639,42 @@ The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Sta +
    +
    +
    + + +TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit, From, Event, To) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      TaskUnit :

      + +
    • +
    • + +

      From :

      + +
    • +
    • + +

      Event :

      + +
    • +
    • + +

      To :

      + +
    • +
    +

    Type list

    diff --git a/docs/Documentation/Task_PICKUP.html b/docs/Documentation/Task_PICKUP.html index 327fe472e..57beddbe9 100644 --- a/docs/Documentation/Task_PICKUP.html +++ b/docs/Documentation/Task_PICKUP.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/Unit.html b/docs/Documentation/Unit.html index 975b845d9..867b1f29a 100644 --- a/docs/Documentation/Unit.html +++ b/docs/Documentation/Unit.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -143,6 +154,12 @@

    Type UNIT

    + + + + - - - - - - - - - - - - - - - - - - - - @@ -211,7 +198,7 @@ @@ -390,42 +377,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -555,6 +506,24 @@ If you want to obtain the complete 3D position including ori�
    + +UNIT:Destroy() + +
    +
    + +

    Destroys the UNIT.

    + +

    Return value

    + +

    #nil: +The DCS Unit is not existing or alive.

    + +
    +
    +
    +
    + UNIT:Find(DCSUnit) @@ -609,79 +578,6 @@ self

    - -UNIT:Flare(FlareColor) - -
    -
    - -

    Signal a flare at the position of the UNIT.

    - -

    Parameter

    - -
    -
    -
    -
    - - -UNIT:FlareGreen() - -
    -
    - -

    Signal a green flare at the position of the UNIT.

    - -
    -
    -
    -
    - - -UNIT:FlareRed() - -
    -
    - -

    Signal a red flare at the position of the UNIT.

    - -
    -
    -
    -
    - - -UNIT:FlareWhite() - -
    -
    - -

    Signal a white flare at the position of the UNIT.

    - -
    -
    -
    -
    - - -UNIT:FlareYellow() - -
    -
    - -

    Signal a yellow flare at the position of the UNIT.

    - -
    -
    -
    -
    - UNIT:GetAmmo() @@ -780,7 +676,7 @@ Category name = Helicopter, Airplane, Ground Unit, Ship

    -

    Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks.

    +

    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.

    @@ -1631,97 +1527,6 @@ The name of the DCS unit.

    #UNIT:

    -
    -
    -
    -
    - - -UNIT:Smoke(SmokeColor, Range) - -
    -
    - -

    Smoke the UNIT.

    - -

    Parameters

    -
      -
    • - -

      SmokeColor :

      - -
    • -
    • - -

      Range :

      - -
    • -
    -
    -
    -
    -
    - - -UNIT:SmokeBlue() - -
    -
    - -

    Smoke the UNIT Blue.

    - -
    -
    -
    -
    - - -UNIT:SmokeGreen() - -
    -
    - -

    Smoke the UNIT Green.

    - -
    -
    -
    -
    - - -UNIT:SmokeOrange() - -
    -
    - -

    Smoke the UNIT Orange.

    - -
    -
    -
    -
    - - -UNIT:SmokeRed() - -
    -
    - -

    Smoke the UNIT Red.

    - -
    -
    -
    -
    - - -UNIT:SmokeWhite() - -
    -
    - -

    Smoke the UNIT White.

    -
    diff --git a/docs/Documentation/UserFlag.html b/docs/Documentation/UserFlag.html new file mode 100644 index 000000000..15d1f7e91 --- /dev/null +++ b/docs/Documentation/UserFlag.html @@ -0,0 +1,351 @@ + + + + + + +
    UNIT:Destroy() +

    Destroys the UNIT.

    +
    UNIT:Find(DCSUnit)

    Finds a UNIT from the _DATABASE using a DCSUnit object.

    @@ -152,36 +169,6 @@
    UNIT:FindByName(UnitName)

    Find a UNIT in the _DATABASE using the name of an existing DCS Unit.

    -
    UNIT:Flare(FlareColor) -

    Signal a flare at the position of the UNIT.

    -
    UNIT:FlareGreen() -

    Signal a green flare at the position of the UNIT.

    -
    UNIT:FlareRed() -

    Signal a red flare at the position of the UNIT.

    -
    UNIT:FlareWhite() -

    Signal a white flare at the position of the UNIT.

    -
    UNIT:FlareYellow() -

    Signal a yellow flare at the position of the UNIT.

    UNIT:GetFuel() -

    Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks.

    +

    Returns relative amount of fuel (from 0.0 to 1.0) the UNIT has in its internal tanks.

    UNIT:ResetEvents()

    Reset the subscriptions.

    -
    UNIT:Smoke(SmokeColor, Range) -

    Smoke the UNIT.

    -
    UNIT:SmokeBlue() -

    Smoke the UNIT Blue.

    -
    UNIT:SmokeGreen() -

    Smoke the UNIT Green.

    -
    UNIT:SmokeOrange() -

    Smoke the UNIT Orange.

    -
    UNIT:SmokeRed() -

    Smoke the UNIT Red.

    -
    UNIT:SmokeWhite() -

    Smoke the UNIT White.

    + + + + +
    USERFLAG +

    USERFLAG class, extends Base#BASE

    + +

    Management of DCS User Flags.

    +
    +

    Type USERFLAG

    + + + + + + + + + + + + + + + + + + + + + +
    USERFLAG:Get(Number) +

    Get the userflag Number.

    +
    USERFLAG:Is(Number) +

    Check if the userflag has a value of Number.

    +
    USERFLAG:New(UserFlagName) +

    USERFLAG Constructor.

    +
    USERFLAG:Set(Number) +

    Set the userflag to a given Number.

    +
    USERFLAG.UserFlagName + +
    + +

    Global(s)

    +
    +
    + + #USERFLAG + +USERFLAG + +
    +
    + +

    USERFLAG class, extends Base#BASE

    + +

    Management of DCS User Flags.

    + + + +

    1. USERFLAG constructor

    + + + + +
    +
    +

    Type UserFlag

    + +

    Type USERFLAG

    +

    Field(s)

    +
    +
    + + +USERFLAG:Get(Number) + +
    +
    + +

    Get the userflag Number.

    + +

    Parameter

    +
      +
    • + +

      Number :

      + +
    • +
    +

    Return value

    + +

    #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.
    +  
    + +
    +
    +
    +
    + + +USERFLAG:Is(Number) + +
    +
    + +

    Check if the userflag has a value of Number.

    + +

    Parameter

    +
      +
    • + +

      #number Number : +The number value to be checked if it is the same as the userflag.

      + +
    • +
    +

    Return value

    + +

    #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
    + +
    +
    +
    +
    + + +USERFLAG:New(UserFlagName) + +
    +
    + +

    USERFLAG Constructor.

    + +

    Parameter

    +
      +
    • + +

      #string UserFlagName : +The name of the userflag, which is a free text string.

      + +
    • +
    +

    Return value

    + +

    #USERFLAG:

    + + +
    +
    +
    +
    + + +USERFLAG:Set(Number) + +
    +
    + +

    Set the userflag to a given Number.

    + +

    Parameter

    +
      +
    • + +

      #number Number : +The number value to be checked if it is the same as the userflag.

      + +
    • +
    +

    Return value

    + +

    #USERFLAG: +The userflag instance.

    + +

    Usage:

    +
      local BlueVictory = USERFLAG:New( "VictoryBlue" )
    +  BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
    +  
    + +
    +
    +
    +
    + + + +USERFLAG.UserFlagName + +
    +
    + + + +
    +
    + +
    + + + + diff --git a/docs/Documentation/UserSound.html b/docs/Documentation/UserSound.html new file mode 100644 index 000000000..853080b6b --- /dev/null +++ b/docs/Documentation/UserSound.html @@ -0,0 +1,419 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module UserSound

    + +

    Core (WIP) -- Manage user sound.

    + + + +
    + +

    Management of DCS User Sound.

    + +
    + +

    Author: Sven Van de Velde (FlightControl)

    + +
    + + +

    Global(s)

    + + + + + +
    USERSOUND +

    USERSOUND class, extends Base#BASE

    + +

    Management of DCS User Sound.

    +
    +

    Type USERSOUND

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    USERSOUND:New(UserSoundFileName) +

    USERSOUND Constructor.

    +
    USERSOUND:SetFileName(UserSoundFileName) +

    Set usersound filename.

    +
    USERSOUND:ToAll() +

    Play the usersound to all players.

    +
    USERSOUND:ToCoalition(Coalition) +

    Play the usersound to the given coalition.

    +
    USERSOUND:ToCountry(Country) +

    Play the usersound to the given country.

    +
    USERSOUND:ToGroup(Group) +

    Play the usersound to the given Group.

    +
    USERSOUND.UserSoundFileName + +
    + +

    Global(s)

    +
    +
    + + #USERSOUND + +USERSOUND + +
    +
    + +

    USERSOUND class, extends Base#BASE

    + +

    Management of DCS User Sound.

    + + + +

    1. USERSOUND constructor

    + + + + +
    +
    +

    Type UserSound

    + +

    Type USERSOUND

    +

    Field(s)

    +
    +
    + + +USERSOUND:New(UserSoundFileName) + +
    +
    + +

    USERSOUND Constructor.

    + +

    Parameter

    +
      +
    • + +

      #string UserSoundFileName : +The filename of the usersound.

      + +
    • +
    +

    Return value

    + +

    #USERSOUND:

    + + +
    +
    +
    +
    + + +USERSOUND:SetFileName(UserSoundFileName) + +
    +
    + +

    Set usersound filename.

    + +

    Parameter

    +
      +
    • + +

      #string UserSoundFileName : +The filename of the usersound.

      + +
    • +
    +

    Return value

    + +

    #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.
    +  
    + +
    +
    +
    +
    + + +USERSOUND:ToAll() + +
    +
    + +

    Play the usersound to all players.

    + +

    Return value

    + +

    #USERSOUND: +The usersound instance.

    + +

    Usage:

    +
      local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
    +  BlueVictory:ToAll() -- Play the sound that Blue has won.
    +  
    + +
    +
    +
    +
    + + +USERSOUND:ToCoalition(Coalition) + +
    +
    + +

    Play the usersound to the given coalition.

    + +

    Parameter

    + +

    Return value

    + +

    #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.
    +  
    + +
    +
    +
    +
    + + +USERSOUND:ToCountry(Country) + +
    +
    + +

    Play the usersound to the given country.

    + +

    Parameter

    + +

    Return value

    + +

    #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.
    +  
    + +
    +
    +
    +
    + + +USERSOUND:ToGroup(Group) + +
    +
    + +

    Play the usersound to the given Group.

    + +

    Parameter

    + +

    Return value

    + +

    #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.
    +  
    + +
    +
    +
    +
    + + + +USERSOUND.UserSoundFileName + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/Utils.html b/docs/Documentation/Utils.html index 9d8cb3142..8b4a74daa 100644 --- a/docs/Documentation/Utils.html +++ b/docs/Documentation/Utils.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -236,12 +247,59 @@ which are excellent tools to be reused in an OO environment!.

    UTILS.FeetToMeters(feet) + + + + UTILS.GetMarkID() + + + + + + UTILS.IsInRadius(InVec2, Vec2, Radius) + + + + + + UTILS.IsInSphere(InVec3, Vec3, Radius) + + + + + + UTILS.IsInstanceOf(object, className) + +

    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

    • +
    + + UTILS.KmphToMps(kmph) + + + + UTILS.KnotsToKmph(knots) + + @@ -260,6 +318,12 @@ which are excellent tools to be reused in an OO environment!.

    UTILS.MetersToNM(meters) + + + + UTILS.MiphToMps(miph) + + @@ -272,6 +336,12 @@ which are excellent tools to be reused in an OO environment!.

    UTILS.MpsToKnots(mps) + + + + UTILS.MpsToMiph(mps) + + @@ -303,6 +373,12 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte UTILS.ToRadian(angle) + + + + UTILS._MarkID + + @@ -608,6 +684,140 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte
    + +UTILS.GetMarkID() + +
    +
    + + + + +

    get a new mark ID for markings

    + +
    +
    +
    +
    + + +UTILS.IsInRadius(InVec2, Vec2, Radius) + +
    +
    + + + + +

    Test if a Vec2 is in a radius of another Vec2

    + +

    Parameters

    +
      +
    • + +

      InVec2 :

      + +
    • +
    • + +

      Vec2 :

      + +
    • +
    • + +

      Radius :

      + +
    • +
    +
    +
    +
    +
    + + +UTILS.IsInSphere(InVec3, Vec3, Radius) + +
    +
    + + + + +

    Test if a Vec3 is in the sphere of another Vec3

    + +

    Parameters

    +
      +
    • + +

      InVec3 :

      + +
    • +
    • + +

      Vec3 :

      + +
    • +
    • + +

      Radius :

      + +
    • +
    +
    +
    +
    +
    + + +UTILS.IsInstanceOf(object, className) + +
    +
    + +

    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

    • +
    + + + +

    Parameters

    +
      +
    • + +

      object : +is the object to be evaluated

      + +
    • +
    • + +

      className : +is the name of the class to evaluate (can be either a string or a Moose class)

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + UTILS.KmphToMps(kmph) @@ -629,6 +839,27 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte
    + +UTILS.KnotsToKmph(knots) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      knots :

      + +
    • +
    +
    +
    +
    +
    + UTILS.KnotsToMps(knots) @@ -692,6 +923,27 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte
    + +UTILS.MiphToMps(miph) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      miph :

      + +
    • +
    +
    +
    +
    +
    + UTILS.MpsToKmph(mps) @@ -721,6 +973,27 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte +

    Parameter

    +
      +
    • + +

      mps :

      + +
    • +
    + +
    +
    +
    + + +UTILS.MpsToMiph(mps) + +
    +
    + + +

    Parameter

    • @@ -843,6 +1116,20 @@ use negative idp for rounding ahead of decimal place, positive for rounding afte
    +
    +
    +
    +
    + + #number + +UTILS._MarkID + +
    +
    + + +
    diff --git a/docs/Documentation/Velocity.html b/docs/Documentation/Velocity.html new file mode 100644 index 000000000..2db4efb84 --- /dev/null +++ b/docs/Documentation/Velocity.html @@ -0,0 +1,688 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Velocity

    + +

    Core -- VELOCITY models a speed, which can be expressed in various formats according the Settings.

    + + + +
    + +

    Author: Sven Van de Velde (FlightControl)

    +

    Contributions:

    + +
    + + +

    Global(s)

    + + + + + + + + + +
    VELOCITY +

    VELOCITY class, extends Base#BASE

    + +

    VELOCITY models a speed, which can be expressed in various formats according the Settings.

    +
    VELOCITY_POSITIONABLE +

    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.

    +
    +

    Type VELOCITY

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VELOCITY:Get() +

    Get the velocity in Mps (meters per second).

    +
    VELOCITY:GetKmph() +

    Get the velocity in Kmph (kilometers per hour).

    +
    VELOCITY:GetMiph() +

    Get the velocity in Miph (miles per hour).

    +
    VELOCITY:GetText(Settings) +

    Get the velocity in text, according the player Settings.

    +
    VELOCITY:New(VelocityMps) +

    VELOCITY Constructor.

    +
    VELOCITY:Set(VelocityMps) +

    Set the velocity in Mps (meters per second).

    +
    VELOCITY:SetKmph(VelocityKmph) +

    Set the velocity in Kmph (kilometers per hour).

    +
    VELOCITY:SetMiph(VelocityMiph) +

    Set the velocity in Miph (miles per hour).

    +
    VELOCITY:ToString(Controllable, Settings, VelocityGroup) +

    Get the velocity in text, according the player or default Settings.

    +
    VELOCITY.Velocity + +
    + +

    Type VELOCITY_POSITIONABLE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VELOCITY_POSITIONABLE:Get() +

    Get the velocity in Mps (meters per second).

    +
    VELOCITY_POSITIONABLE:GetKmph() +

    Get the velocity in Kmph (kilometers per hour).

    +
    VELOCITY_POSITIONABLE:GetMiph() +

    Get the velocity in Miph (miles per hour).

    +
    VELOCITY_POSITIONABLE:New(Positionable) +

    VELOCITY_POSITIONABLE Constructor.

    +
    VELOCITY_POSITIONABLE.Positionable + +
    VELOCITY_POSITIONABLE:ToString() +

    Get the velocity in text, according the player or default Settings.

    +
    VELOCITY_POSITIONABLE.Velocity + +
    + +

    Global(s)

    +
    +
    + + #VELOCITY + +VELOCITY + +
    +
    + +

    VELOCITY class, extends Base#BASE

    + +

    VELOCITY models a speed, which can be expressed in various formats according the Settings.

    + + + +

    1. VELOCITY constructor

    + + + + +
    +
    +
    +
    + + #VELOCITY_POSITIONABLE + +VELOCITY_POSITIONABLE + +
    +
    + +

    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

    + + + + +
    +
    +

    Type Velocity

    + +

    Type VELOCITY

    +

    Field(s)

    +
    +
    + + +VELOCITY:Get() + +
    +
    + +

    Get the velocity in Mps (meters per second).

    + +

    Return value

    + +

    #number: +The velocity in meters per second.

    + +
    +
    +
    +
    + + +VELOCITY:GetKmph() + +
    +
    + +

    Get the velocity in Kmph (kilometers per hour).

    + +

    Return value

    + +

    #number: +The velocity in kilometers per hour.

    + +
    +
    +
    +
    + + +VELOCITY:GetMiph() + +
    +
    + +

    Get the velocity in Miph (miles per hour).

    + +

    Return value

    + +

    #number: +The velocity in miles per hour.

    + +
    +
    +
    +
    + + +VELOCITY:GetText(Settings) + +
    +
    + +

    Get the velocity in text, according the player Settings.

    + +

    Parameter

    + +

    Return value

    + +

    #string: +The velocity in text.

    + +
    +
    +
    +
    + + +VELOCITY:New(VelocityMps) + +
    +
    + +

    VELOCITY Constructor.

    + +

    Parameter

    +
      +
    • + +

      #number VelocityMps : +The velocity in meters per second.

      + +
    • +
    +

    Return value

    + +

    #VELOCITY:

    + + +
    +
    +
    +
    + + +VELOCITY:Set(VelocityMps) + +
    +
    + +

    Set the velocity in Mps (meters per second).

    + +

    Parameter

    +
      +
    • + +

      #number VelocityMps : +The velocity in meters per second.

      + +
    • +
    +

    Return value

    + +

    #VELOCITY:

    + + +
    +
    +
    +
    + + +VELOCITY:SetKmph(VelocityKmph) + +
    +
    + +

    Set the velocity in Kmph (kilometers per hour).

    + +

    Parameter

    +
      +
    • + +

      #number VelocityKmph : +The velocity in kilometers per hour.

      + +
    • +
    +

    Return value

    + +

    #VELOCITY:

    + + +
    +
    +
    +
    + + +VELOCITY:SetMiph(VelocityMiph) + +
    +
    + +

    Set the velocity in Miph (miles per hour).

    + +

    Parameter

    +
      +
    • + +

      #number VelocityMiph : +The velocity in miles per hour.

      + +
    • +
    +

    Return value

    + +

    #VELOCITY:

    + + +
    +
    +
    +
    + + +VELOCITY:ToString(Controllable, Settings, VelocityGroup) + +
    +
    + +

    Get the velocity in text, according the player or default Settings.

    + +

    Parameters

    + +

    Return value

    + +

    #string: +The velocity in text according the player or default Settings

    + +
    +
    +
    +
    + + + +VELOCITY.Velocity + +
    +
    + + + +
    +
    + +

    Type VELOCITY_POSITIONABLE

    +

    Field(s)

    +
    +
    + + +VELOCITY_POSITIONABLE:Get() + +
    +
    + +

    Get the velocity in Mps (meters per second).

    + +

    Return value

    + +

    #number: +The velocity in meters per second.

    + +
    +
    +
    +
    + + +VELOCITY_POSITIONABLE:GetKmph() + +
    +
    + +

    Get the velocity in Kmph (kilometers per hour).

    + +

    Return value

    + +

    #number: +The velocity in kilometers per hour.

    + +
    +
    +
    +
    + + +VELOCITY_POSITIONABLE:GetMiph() + +
    +
    + +

    Get the velocity in Miph (miles per hour).

    + +

    Return value

    + +

    #number: +The velocity in miles per hour.

    + +
    +
    +
    +
    + + +VELOCITY_POSITIONABLE:New(Positionable) + +
    +
    + +

    VELOCITY_POSITIONABLE Constructor.

    + +

    Parameter

    + +

    Return value

    + +

    #VELOCITY_POSITIONABLE:

    + + +
    +
    +
    +
    + + + +VELOCITY_POSITIONABLE.Positionable + +
    +
    + + + +
    +
    +
    +
    + + +VELOCITY_POSITIONABLE:ToString() + +
    +
    + +

    Get the velocity in text, according the player or default Settings.

    + +

    Return value

    + +

    #string: +The velocity in text according the player or default Settings

    + +
    +
    +
    +
    + + + +VELOCITY_POSITIONABLE.Velocity + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/Zone.html b/docs/Documentation/Zone.html index da7814e14..ffd633f60 100644 --- a/docs/Documentation/Zone.html +++ b/docs/Documentation/Zone.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -327,6 +338,12 @@ ZONE_BASE:New(ZoneName)

    ZONE_BASE constructor

    + + + + ZONE_BASE:SetName(ZoneName) + +

    Sets the name of the zone.

    @@ -386,6 +403,12 @@

    Type ZONE_GROUP

    + + + + + + + +
    ZONE_GROUP:GetRandomPointVec2(inner, outer) +

    Returns a Point#POINT_VEC2 object reflecting a random 2D location within the zone.

    +
    ZONE_GROUP:GetRandomVec2()

    Returns a random location within the zone of the Group.

    @@ -410,7 +433,13 @@
    ZONE_POLYGON:New(ZoneName, ZoneGroup) -

    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.

    +
    ZONE_POLYGON:NewFromGroupName(ZoneName, GroupName) +

    Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the Group#GROUP defined within the Mission Editor.

    @@ -433,6 +462,12 @@ ZONE_POLYGON_BASE:GetBoundingSquare()

    Get the bounding square the zone.

    + + + + ZONE_POLYGON_BASE:GetRandomCoordinate() + +

    Return a Point#COORDINATE object representing a random 3D point at landheight within the zone.

    @@ -488,7 +523,13 @@ - ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth) + ZONE_RADIUS:CountScannedCoalitions() + + + + + + ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth, AddHeight)

    Flares the zone boundaries in a color.

    @@ -497,6 +538,12 @@ ZONE_RADIUS:GetRadius()

    Returns the radius of the zone.

    + + + + ZONE_RADIUS:GetRandomCoordinate(inner, outer) + +

    Returns a Point#COORDINATE object reflecting a random 3D location within the zone.

    @@ -515,6 +562,24 @@ ZONE_RADIUS:GetRandomVec2(inner, outer)

    Returns a random Vec2 location within the zone.

    + + + + ZONE_RADIUS:GetScannedCoalition(Coalition) + +

    Get Coalitions of the units in the Zone, or Check if there are units of the given Coalition in the Zone.

    + + + + ZONE_RADIUS:GetScannedScenery() + + + + + + ZONE_RADIUS:GetScannedSceneryType(SceneryType) + + @@ -527,6 +592,36 @@ ZONE_RADIUS:GetVec3(Height)

    Returns the DCSTypes#Vec3 of the ZONE_RADIUS.

    + + + + ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition) + +

    Is All in Zone of Coalition?

    + + + + ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition) + +

    Is All in Zone of Other Coalition?

    + + + + ZONE_RADIUS:IsNoneInZone() + +

    Is None in Zone?

    + + + + ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition) + +

    Is None in Zone of Coalition?

    + + + + ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition) + +

    Is Some in Zone of Coalition?

    @@ -551,6 +646,24 @@ ZONE_RADIUS.Radius

    The radius of the zone.

    + + + + ZONE_RADIUS:Scan(ObjectCategories, Coalition) + +

    Scan the zone

    + + + + ZONE_RADIUS.ScanData + + + + + + ZONE_RADIUS:SearchZone(ObjectCategories, EvaluateFunction) + +

    Searches the zone

    @@ -566,7 +679,7 @@ - ZONE_RADIUS:SmokeZone(SmokeColor, Points) + ZONE_RADIUS:SmokeZone(SmokeColor, Points, AddHeight, AddOffSet, AngleOffset)

    Smokes the zone boundaries in a color.

    @@ -660,8 +773,10 @@ +

    Each zone implements two polymorphic functions defined in Zone#ZONE_BASE:

      @@ -1283,6 +1398,33 @@ Name of the zone.

      #ZONE_BASE: self

      + +
    +
    +
    + + +ZONE_BASE:SetName(ZoneName) + +
    +
    + +

    Sets the name of the zone.

    + +

    Parameter

    +
      +
    • + +

      #string ZoneName : +The name of the zone.

      + +
    • +
    +

    Return value

    + +

    #ZONE_BASE:

    + +
    @@ -1425,6 +1567,39 @@ The smoke color.

    + +ZONE_GROUP:GetRandomPointVec2(inner, outer) + +
    +
    + +

    Returns a Point#POINT_VEC2 object reflecting a random 2D location within the zone.

    + +

    Parameters

    +
      +
    • + +

      #number inner : +(optional) Minimal distance from the center of the zone. Default is 0.

      + +
    • +
    • + +

      #number outer : +(optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.

      + +
    • +
    +

    Return value

    + +

    Core.Point#POINT_VEC2: +The Point#POINT_VEC2 object reflecting the random 3D location within the zone.

    + +
    +
    +
    +
    + ZONE_GROUP:GetRandomVec2() @@ -1509,7 +1684,7 @@ self

    -

    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.

    @@ -1534,6 +1709,42 @@ The GROUP waypoints as defined within the Mission Editor define the polygon shap

    #ZONE_POLYGON: self

    +
    +
    +
    +
    + + +ZONE_POLYGON:NewFromGroupName(ZoneName, GroupName) + +
    +
    + +

    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.

    + +

    Parameters

    +
      +
    • + +

      #string ZoneName : +Name of the zone.

      + +
    • +
    • + +

      #string GroupName : +The group name of the GROUP defining the waypoints within the Mission Editor to define the polygon shape.

      + +
    • +
    +

    Return value

    + +

    #ZONE_POLYGON: +self

    +
    @@ -1600,6 +1811,24 @@ self

    #ZONEPOLYGONBASE.BoundingSquare: The bounding square.

    + +
    +
    +
    + + +ZONE_POLYGON_BASE:GetRandomCoordinate() + +
    +
    + +

    Return a Point#COORDINATE object representing a random 3D point at landheight within the zone.

    + +

    Return value

    + +

    Core.Point#COORDINATE:

    + +
    @@ -1816,13 +2045,26 @@ If true the tyres will be destroyed.

    #ZONE_RADIUS: self

    + +
    +
    +
    + + +ZONE_RADIUS:CountScannedCoalitions() + +
    +
    + + +
    -ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth) +ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth, AddHeight)
    @@ -1848,6 +2090,12 @@ The flare color.

    Dcs.DCSTypes#Azimuth Azimuth : (optional) Azimuth The azimuth of the flare.

    + +
  • + +

    #number AddHeight : +(optional) The height to be added for the smoke.

    +
  • Return value

    @@ -1873,6 +2121,39 @@ self

    Dcs.DCSTypes#Distance: The radius of the zone.

    +
    +
    +
    +
    + + +ZONE_RADIUS:GetRandomCoordinate(inner, outer) + +
    +
    + +

    Returns a Point#COORDINATE object reflecting a random 3D location within the zone.

    + +

    Parameters

    +
      +
    • + +

      #number inner : +(optional) Minimal distance from the center of the zone. Default is 0.

      + +
    • +
    • + +

      #number outer : +(optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.

      + +
    • +
    +

    Return value

    + +

    Core.Point#COORDINATE:

    + +
    @@ -1977,6 +2258,71 @@ The random location within the zone.

    + +ZONE_RADIUS:GetScannedCoalition(Coalition) + +
    +
    + +

    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

    + +

    Parameter

    +
      +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #table:

    + + +
    +
    +
    +
    + + +ZONE_RADIUS:GetScannedScenery() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_RADIUS:GetScannedSceneryType(SceneryType) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      SceneryType :

      + +
    • +
    +
    +
    +
    +
    + ZONE_RADIUS:GetVec2() @@ -2017,6 +2363,128 @@ The height to add to the land height where the center of the zone is located.

    Dcs.DCSTypes#Vec3: The point of the zone.

    + +
    +
    +
    + + +ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition) + +
    +
    + +

    Is All in Zone of Coalition?

    + +

    Parameter

    +
      +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition) + +
    +
    + +

    Is All in Zone of Other Coalition?

    + +

    Parameter

    +
      +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_RADIUS:IsNoneInZone() + +
    +
    + +

    Is None in Zone?

    + +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition) + +
    +
    + +

    Is None in Zone of Coalition?

    + +

    Parameter

    +
      +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition) + +
    +
    + +

    Is Some in Zone of Coalition?

    + +

    Parameter

    +
      +
    • + +

      Coalition :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + +
    @@ -2129,6 +2597,73 @@ self

    + +ZONE_RADIUS:Scan(ObjectCategories, Coalition) + +
    +
    + +

    Scan the zone

    + +

    Parameters

    +
      +
    • + +

      ObjectCategories :

      + +
    • +
    • + +

      Coalition :

      + +
    • +
    +
    +
    +
    +
    + + + +ZONE_RADIUS.ScanData + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_RADIUS:SearchZone(ObjectCategories, EvaluateFunction) + +
    +
    + +

    Searches the zone

    + +

    Parameters

    +
      +
    • + +

      ObjectCategories : +A list of categories, which are members of Object.Category

      + +
    • +
    • + +

      EvaluateFunction :

      + +
    • +
    +
    +
    +
    +
    + ZONE_RADIUS:SetRadius(Radius) @@ -2184,7 +2719,7 @@ The new location of the zone.

    -ZONE_RADIUS:SmokeZone(SmokeColor, Points) +ZONE_RADIUS:SmokeZone(SmokeColor, Points, AddHeight, AddOffSet, AngleOffset)
    @@ -2204,6 +2739,23 @@ The smoke color.

    #number Points : (optional) The amount of points in the circle.

    + +
  • + +

    #number AddHeight : +(optional) The height to be added for the smoke.

    + +
  • +
  • + +

    #number AddOffSet : +(optional) The angle to be added for the smoking start position.

    + +
  • +
  • + +

    AngleOffset :

    +
  • Return value

    diff --git a/docs/Documentation/ZoneCaptureCoalition.html b/docs/Documentation/ZoneCaptureCoalition.html new file mode 100644 index 000000000..1aac7fba6 --- /dev/null +++ b/docs/Documentation/ZoneCaptureCoalition.html @@ -0,0 +1,1074 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module ZoneCaptureCoalition

    + +

    Functional -- (WIP R2.3) Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.

    + + + +
    + +

    Banner Image

    + +
    + +

    Contributions: Millertime: Concept

    +

    Author: Sven Van de Velde (FlightControl)

    + +
    + + +

    Global(s)

    + + + + + +
    ZONE_CAPTURE_COALITION +

    ZONE_CAPTURE_COALITION class, extends ZoneGoalCoalition#ZONEGOALCOALITION

    + +

    Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.

    +
    +

    Type ZONE_CAPTURE_COALITION

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ZONE_CAPTURE_COALITION:Attack() +

    Attack Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:Capture() +

    Capture Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:Empty() +

    Empty Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:Guard() +

    Guard Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:IsAttacked() + +
    ZONE_CAPTURE_COALITION:IsCaptured() + +
    ZONE_CAPTURE_COALITION:IsEmpty() + +
    ZONE_CAPTURE_COALITION:IsGuarded() + +
    ZONE_CAPTURE_COALITION:Mark() +

    Mark.

    +
    ZONE_CAPTURE_COALITION.MarkBlue + +
    ZONE_CAPTURE_COALITION.MarkRed + +
    ZONE_CAPTURE_COALITION:New(Zone, Coalition) +

    ZONECAPTURECOALITION Constructor.

    +
    ZONE_CAPTURE_COALITION:OnAfterAttack(From, Event, To) +

    Attack Handler OnAfter for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnAfterCapture(From, Event, To) +

    Capture Handler OnAfter for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnAfterEmpty(From, Event, To) +

    Empty Handler OnAfter for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnAfterGuard(From, Event, To) +

    Guard Handler OnAfter for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnBeforeAttack(From, Event, To) +

    Attack Handler OnBefore for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnBeforeCapture(From, Event, To) +

    Capture Handler OnBefore for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnBeforeEmpty(From, Event, To) +

    Empty Handler OnBefore for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:OnBeforeGuard(From, Event, To) +

    Guard Handler OnBefore for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION.ScheduleStatusZone + +
    ZONE_CAPTURE_COALITION.SmokeScheduler + +
    ZONE_CAPTURE_COALITION.States + +
    ZONE_CAPTURE_COALITION:StatusZone() +

    Check status Coalition ownership.

    +
    ZONE_CAPTURE_COALITION:__Attack(Delay) +

    Attack Asynchronous Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:__Capture(Delay) +

    Capture Asynchronous Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:__Empty(Delay) +

    Empty Asynchronous Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:__Guard(Delay) +

    Guard Asynchronous Trigger for ZONE_CAPTURE_COALITION

    +
    ZONE_CAPTURE_COALITION:onafterGuard() +

    When started, check the Coalition status.

    +
    ZONE_CAPTURE_COALITION:onenterAttacked() + +
    ZONE_CAPTURE_COALITION:onenterCaptured() + +
    ZONE_CAPTURE_COALITION:onenterEmpty() + +
    ZONE_CAPTURE_COALITION:onenterGuarded() +

    Bound.

    +
    + +

    Global(s)

    +
    +
    + + #ZONE_CAPTURE_COALITION + +ZONE_CAPTURE_COALITION + +
    +
    + +

    ZONE_CAPTURE_COALITION class, extends ZoneGoalCoalition#ZONEGOALCOALITION

    + +

    Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.

    + + + +
    + +

    Banner Image

    + +
    + +

    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

    + + + +

    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. +
    • +
    + +
    +
    +

    Type ZoneCaptureCoalition

    + +

    Type ZONE_CAPTURE_COALITION

    +

    Field(s)

    +
    +
    + + +ZONE_CAPTURE_COALITION:Attack() + +
    +
    + +

    Attack Trigger for ZONE_CAPTURE_COALITION

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:Capture() + +
    +
    + +

    Capture Trigger for ZONE_CAPTURE_COALITION

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:Empty() + +
    +
    + +

    Empty Trigger for ZONE_CAPTURE_COALITION

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:Guard() + +
    +
    + +

    Guard Trigger for ZONE_CAPTURE_COALITION

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:IsAttacked() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:IsCaptured() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:IsEmpty() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:IsGuarded() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:Mark() + +
    +
    + +

    Mark.

    + +
    +
    +
    +
    + + + +ZONE_CAPTURE_COALITION.MarkBlue + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_CAPTURE_COALITION.MarkRed + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:New(Zone, Coalition) + +
    +
    + +

    ZONECAPTURECOALITION Constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #ZONECAPTURECOALITION:

    + + +

    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.
    + 
    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnAfterAttack(From, Event, To) + +
    +
    + +

    Attack Handler OnAfter for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnAfterCapture(From, Event, To) + +
    +
    + +

    Capture Handler OnAfter for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnAfterEmpty(From, Event, To) + +
    +
    + +

    Empty Handler OnAfter for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnAfterGuard(From, Event, To) + +
    +
    + +

    Guard Handler OnAfter for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnBeforeAttack(From, Event, To) + +
    +
    + +

    Attack Handler OnBefore for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnBeforeCapture(From, Event, To) + +
    +
    + +

    Capture Handler OnBefore for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnBeforeEmpty(From, Event, To) + +
    +
    + +

    Empty Handler OnBefore for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:OnBeforeGuard(From, Event, To) + +
    +
    + +

    Guard Handler OnBefore for ZONE_CAPTURE_COALITION

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + + +ZONE_CAPTURE_COALITION.ScheduleStatusZone + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_CAPTURE_COALITION.SmokeScheduler + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_CAPTURE_COALITION.States + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:StatusZone() + +
    +
    + +

    Check status Coalition ownership.

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:__Attack(Delay) + +
    +
    + +

    Attack Asynchronous Trigger for ZONE_CAPTURE_COALITION

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:__Capture(Delay) + +
    +
    + +

    Capture Asynchronous Trigger for ZONE_CAPTURE_COALITION

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:__Empty(Delay) + +
    +
    + +

    Empty Asynchronous Trigger for ZONE_CAPTURE_COALITION

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:__Guard(Delay) + +
    +
    + +

    Guard Asynchronous Trigger for ZONE_CAPTURE_COALITION

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:onafterGuard() + +
    +
    + +

    When started, check the Coalition status.

    + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:onenterAttacked() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:onenterCaptured() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:onenterEmpty() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_CAPTURE_COALITION:onenterGuarded() + +
    +
    + +

    Bound.

    + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/ZoneGoal.html b/docs/Documentation/ZoneGoal.html new file mode 100644 index 000000000..564fc72b7 --- /dev/null +++ b/docs/Documentation/ZoneGoal.html @@ -0,0 +1,569 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module ZoneGoal

    + +

    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)

    + +
    + + +

    Global(s)

    + + + + + +
    ZONE_GOAL +

    ZONE_GOAL class, extends Fsm#FSM

    + +

    ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.

    +
    +

    Type ZONE_GOAL

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ZONE_GOAL:Flare(FlareColor) +

    Flare the center of the zone.

    +
    ZONE_GOAL:GetZone() +

    Get the Zone

    +
    ZONE_GOAL:GetZoneName() +

    Get the name of the ProtectZone

    +
    ZONE_GOAL.Goal + +
    ZONE_GOAL:MonitorDestroyedUnits() +

    Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.

    +
    ZONE_GOAL:New(Zone) +

    ZONE_GOAL Constructor.

    +
    ZONE_GOAL:OnAfterDestroyedUnit(From, Event, To, DestroyedUnit, PlayerName) +

    DestroyedUnit Handler OnAfter for ZONE_GOAL

    +
    ZONE_GOAL:Smoke(SmokeColor) +

    Smoke the center of theh zone.

    +
    ZONE_GOAL.SmokeColor + +
    ZONE_GOAL.SmokeScheduler + +
    ZONE_GOAL.SmokeTime + +
    ZONE_GOAL:StatusSmoke() +

    Check status Smoke.

    +
    ZONE_GOAL.Zone + +
    ZONE_GOAL:__Destroyed(EventData) + +
    ZONE_GOAL:onafterGuard() +

    When started, check the Smoke and the Zone status.

    +
    + +

    Global(s)

    +
    +
    + + #ZONE_GOAL + +ZONE_GOAL + +
    +
    + +

    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

    + + + +

    2. ZONE_GOAL is a finite state machine (FSM).

    + +

    2.1 ZONE_GOAL States

    + +
      +
    • None: Initial State
    • +
    + +

    2.2 ZONE_GOAL Events

    + + + + +
    +
    +

    Type ZoneGoal

    + +

    Type SMOKECOLOR.Color

    + +

    Type ZONE_GOAL

    +

    Field(s)

    +
    +
    + + +ZONE_GOAL:Flare(FlareColor) + +
    +
    + +

    Flare the center of the zone.

    + +

    Parameter

    + +
    +
    +
    +
    + + +ZONE_GOAL:GetZone() + +
    +
    + +

    Get the Zone

    + +

    Return value

    + +

    Core.Zone#ZONE_BASE:

    + + +
    +
    +
    +
    + + +ZONE_GOAL:GetZoneName() + +
    +
    + +

    Get the name of the ProtectZone

    + +

    Return value

    + +

    #string:

    + + +
    +
    +
    +
    + + + +ZONE_GOAL.Goal + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL:MonitorDestroyedUnits() + +
    +
    + +

    Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.

    + +
    +
    +
    +
    + + +ZONE_GOAL:New(Zone) + +
    +
    + +

    ZONE_GOAL Constructor.

    + +

    Parameter

    + +

    Return value

    + +

    #ZONE_GOAL:

    + + +
    +
    +
    +
    + + +ZONE_GOAL:OnAfterDestroyedUnit(From, Event, To, DestroyedUnit, PlayerName) + +
    +
    + +

    DestroyedUnit Handler OnAfter for ZONE_GOAL

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    • + +

      Wrapper.Unit#UNIT DestroyedUnit : +The destroyed unit.

      + +
    • +
    • + +

      #string PlayerName : +The name of the player.

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL:Smoke(SmokeColor) + +
    +
    + +

    Smoke the center of theh zone.

    + +

    Parameter

    + +
    +
    +
    +
    + + + +ZONE_GOAL.SmokeColor + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_GOAL.SmokeScheduler + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL.SmokeTime + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL:StatusSmoke() + +
    +
    + +

    Check status Smoke.

    + +
    +
    +
    +
    + + Core.Zone#ZONE_BASE + +ZONE_GOAL.Zone + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL:__Destroyed(EventData) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + +ZONE_GOAL:onafterGuard() + +
    +
    + +

    When started, check the Smoke and the Zone status.

    + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/ZoneGoalCargo.html b/docs/Documentation/ZoneGoalCargo.html new file mode 100644 index 000000000..a78db9225 --- /dev/null +++ b/docs/Documentation/ZoneGoalCargo.html @@ -0,0 +1,1151 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module ZoneGoalCargo

    + +

    Functional (WIP) -- Base class that models processes to achieve goals involving a Zone and Cargo.

    + + + +
    + +

    ZONEGOALCARGO 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)

    + +
    + + +

    Global(s)

    + + + + + +
    ZONE_GOAL_CARGO +

    ZONEGOALCARGO class, extends ZoneGoal#ZONE_GOAL

    + +

    ZONEGOALCARGO models processes that have a Goal with a defined achievement involving a Zone and Cargo.

    +
    +

    Type ZONE_GOAL_CARGO

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ZONE_GOAL_CARGO:Attack() +

    Attack Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:Capture() +

    Capture Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO.Coalition + +
    ZONE_GOAL_CARGO:Empty() +

    Empty Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:GetCoalition() +

    Get the owning coalition of the zone.

    +
    ZONE_GOAL_CARGO:GetCoalitionName() +

    Get the owning coalition name of the zone.

    +
    ZONE_GOAL_CARGO:Guard() +

    Guard Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:IsAttacked() + +
    ZONE_GOAL_CARGO:IsCaptured() + +
    ZONE_GOAL_CARGO:IsEmpty() + +
    ZONE_GOAL_CARGO:IsGuarded() + +
    ZONE_GOAL_CARGO:Mark() +

    Mark.

    +
    ZONE_GOAL_CARGO.MarkBlue + +
    ZONE_GOAL_CARGO.MarkRed + +
    ZONE_GOAL_CARGO:New(Zone, Coalition) +

    ZONEGOALCARGO Constructor.

    +
    ZONE_GOAL_CARGO:OnAfterAttack(From, Event, To) +

    Attack Handler OnAfter for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnAfterCapture(From, Event, To) +

    Capture Handler OnAfter for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnAfterEmpty(From, Event, To) +

    Empty Handler OnAfter for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnAfterGuard(From, Event, To) +

    Guard Handler OnAfter for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnBeforeAttack(From, Event, To) +

    Attack Handler OnBefore for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnBeforeCapture(From, Event, To) +

    Capture Handler OnBefore for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnBeforeEmpty(From, Event, To) +

    Empty Handler OnBefore for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:OnBeforeGuard(From, Event, To) +

    Guard Handler OnBefore for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO.ScheduleStatusZone + +
    ZONE_GOAL_CARGO:SetCoalition(Coalition) +

    Set the owning coalition of the zone.

    +
    ZONE_GOAL_CARGO.SmokeScheduler + +
    ZONE_GOAL_CARGO.States + +
    ZONE_GOAL_CARGO:StatusZone() +

    Check status Coalition ownership.

    +
    ZONE_GOAL_CARGO:__Attack(Delay) +

    Attack Asynchronous Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:__Capture(Delay) +

    Capture Asynchronous Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:__Empty(Delay) +

    Empty Asynchronous Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:__Guard(Delay) +

    Guard Asynchronous Trigger for ZONEGOALCARGO

    +
    ZONE_GOAL_CARGO:onafterGuard() +

    When started, check the Coalition status.

    +
    ZONE_GOAL_CARGO:onenterAttacked() + +
    ZONE_GOAL_CARGO:onenterCaptured() + +
    ZONE_GOAL_CARGO:onenterEmpty() + +
    ZONE_GOAL_CARGO:onenterGuarded() +

    Bound.

    +
    + +

    Global(s)

    +
    +
    + + #ZONE_GOAL_CARGO + +ZONE_GOAL_CARGO + +
    +
    + +

    ZONEGOALCARGO class, extends ZoneGoal#ZONE_GOAL

    + +

    ZONEGOALCARGO 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. ZONEGOALCARGO constructor

    + + + +

    2. ZONEGOALCARGO is a finite state machine (FSM).

    + +

    2.1 ZONEGOALCARGO 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 ZONEGOALCARGO 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 ZONEGOALCARGO State Machine

    + + +
    +
    +

    Type ZoneGoalCargo

    + +

    Type ZONE_GOAL_CARGO

    +

    Field(s)

    +
    +
    + + +ZONE_GOAL_CARGO:Attack() + +
    +
    + +

    Attack Trigger for ZONEGOALCARGO

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:Capture() + +
    +
    + +

    Capture Trigger for ZONEGOALCARGO

    + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.Coalition + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:Empty() + +
    +
    + +

    Empty Trigger for ZONEGOALCARGO

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:GetCoalition() + +
    +
    + +

    Get the owning coalition of the zone.

    + +

    Return value

    + +

    DCSCoalition.DCSCoalition#coalition: +Coalition.

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:GetCoalitionName() + +
    +
    + +

    Get the owning coalition name of the zone.

    + +

    Return value

    + +

    #string: +Coalition name.

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:Guard() + +
    +
    + +

    Guard Trigger for ZONEGOALCARGO

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:IsAttacked() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:IsCaptured() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:IsEmpty() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:IsGuarded() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:Mark() + +
    +
    + +

    Mark.

    + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.MarkBlue + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.MarkRed + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:New(Zone, Coalition) + +
    +
    + +

    ZONEGOALCARGO Constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #ZONEGOALCARGO:

    + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnAfterAttack(From, Event, To) + +
    +
    + +

    Attack Handler OnAfter for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnAfterCapture(From, Event, To) + +
    +
    + +

    Capture Handler OnAfter for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnAfterEmpty(From, Event, To) + +
    +
    + +

    Empty Handler OnAfter for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnAfterGuard(From, Event, To) + +
    +
    + +

    Guard Handler OnAfter for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnBeforeAttack(From, Event, To) + +
    +
    + +

    Attack Handler OnBefore for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnBeforeCapture(From, Event, To) + +
    +
    + +

    Capture Handler OnBefore for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnBeforeEmpty(From, Event, To) + +
    +
    + +

    Empty Handler OnBefore for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:OnBeforeGuard(From, Event, To) + +
    +
    + +

    Guard Handler OnBefore for ZONEGOALCARGO

    + +

    Parameters

    +
      +
    • + +

      #string From :

      + +
    • +
    • + +

      #string Event :

      + +
    • +
    • + +

      #string To :

      + +
    • +
    +

    Return value

    + +

    #boolean:

    + + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.ScheduleStatusZone + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:SetCoalition(Coalition) + +
    +
    + +

    Set the owning coalition of the zone.

    + +

    Parameter

    + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.SmokeScheduler + +
    +
    + + + +
    +
    +
    +
    + + + +ZONE_GOAL_CARGO.States + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:StatusZone() + +
    +
    + +

    Check status Coalition ownership.

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:__Attack(Delay) + +
    +
    + +

    Attack Asynchronous Trigger for ZONEGOALCARGO

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:__Capture(Delay) + +
    +
    + +

    Capture Asynchronous Trigger for ZONEGOALCARGO

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:__Empty(Delay) + +
    +
    + +

    Empty Asynchronous Trigger for ZONEGOALCARGO

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:__Guard(Delay) + +
    +
    + +

    Guard Asynchronous Trigger for ZONEGOALCARGO

    + +

    Parameter

    +
      +
    • + +

      #number Delay :

      + +
    • +
    +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:onafterGuard() + +
    +
    + +

    When started, check the Coalition status.

    + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:onenterAttacked() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:onenterCaptured() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:onenterEmpty() + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_CARGO:onenterGuarded() + +
    +
    + +

    Bound.

    + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/ZoneGoalCoalition.html b/docs/Documentation/ZoneGoalCoalition.html new file mode 100644 index 000000000..054c9ff22 --- /dev/null +++ b/docs/Documentation/ZoneGoalCoalition.html @@ -0,0 +1,367 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module ZoneGoalCoalition

    + +

    Functional (WIP) -- Base class that models processes to achieve goals involving a Zone for a Coalition.

    + + + +
    + +

    ZONEGOALCOALITION 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)

    + +
    + + +

    Global(s)

    + + + + + +
    ZONE_GOAL_COALITION +

    ZONEGOALCOALITION class, extends ZoneGoal#ZONE_GOAL

    + +

    ZONEGOALCOALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.

    +
    +

    Type ZONE_GOAL_COALITION

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ZONE_GOAL_COALITION.Coalition + +
    ZONE_GOAL_COALITION:GetCoalition() +

    Get the owning coalition of the zone.

    +
    ZONE_GOAL_COALITION:GetCoalitionName() +

    Get the owning coalition name of the zone.

    +
    ZONE_GOAL_COALITION:New(Zone, Coalition) +

    ZONEGOALCOALITION Constructor.

    +
    ZONE_GOAL_COALITION:SetCoalition(Coalition) +

    Set the owning coalition of the zone.

    +
    ZONE_GOAL_COALITION.States + +
    ZONE_GOAL_COALITION:StatusZone() +

    Check status Coalition ownership.

    +
    + +

    Global(s)

    +
    +
    + + #ZONE_GOAL_COALITION + +ZONE_GOAL_COALITION + +
    +
    + +

    ZONEGOALCOALITION class, extends ZoneGoal#ZONE_GOAL

    + +

    ZONEGOALCOALITION 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. ZONEGOALCOALITION constructor

    + + + +

    2. ZONEGOALCOALITION is a finite state machine (FSM).

    + +

    2.1 ZONEGOALCOALITION States

    + +

    2.2 ZONEGOALCOALITION Events

    + +

    2.3 ZONEGOALCOALITION State Machine

    + + +
    +
    +

    Type ZoneGoalCoalition

    + +

    Type ZONE_GOAL_COALITION

    +

    Field(s)

    +
    +
    + + + +ZONE_GOAL_COALITION.Coalition + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_COALITION:GetCoalition() + +
    +
    + +

    Get the owning coalition of the zone.

    + +

    Return value

    + +

    DCSCoalition.DCSCoalition#coalition: +Coalition.

    + +
    +
    +
    +
    + + +ZONE_GOAL_COALITION:GetCoalitionName() + +
    +
    + +

    Get the owning coalition name of the zone.

    + +

    Return value

    + +

    #string: +Coalition name.

    + +
    +
    +
    +
    + + +ZONE_GOAL_COALITION:New(Zone, Coalition) + +
    +
    + +

    ZONEGOALCOALITION Constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #ZONEGOALCOALITION:

    + + +
    +
    +
    +
    + + +ZONE_GOAL_COALITION:SetCoalition(Coalition) + +
    +
    + +

    Set the owning coalition of the zone.

    + +

    Parameter

    + +
    +
    +
    +
    + + + +ZONE_GOAL_COALITION.States + +
    +
    + + + +
    +
    +
    +
    + + +ZONE_GOAL_COALITION:StatusZone() + +
    +
    + +

    Check status Coalition ownership.

    + +
    +
    + +
    + +
    + + diff --git a/docs/Documentation/env.html b/docs/Documentation/env.html index 09dbbfcd0..7b8b8a626 100644 --- a/docs/Documentation/env.html +++ b/docs/Documentation/env.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/index.html b/docs/Documentation/index.html index 5b940c5a5..4a5acfd58 100644 --- a/docs/Documentation/index.html +++ b/docs/Documentation/index.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • @@ -129,7 +140,7 @@
    -

    AI CAP classes makes AI Controllables execute a Combat Air Patrol.

    +

    AI CAP classes makes AI Groups execute a Combat Air Patrol.

    @@ -159,7 +170,7 @@
    -

    AI PATROL classes makes AI Controllables execute an Patrol.

    +

    AI PATROL classes makes AI Groups execute an Patrol.

    @@ -249,6 +260,12 @@ even when there are hardly any players in the mission.


    AI PATROL classes makes AI Controllables execute an Patrol.

    + + + + ATC_Ground + +

    Functional -- The ATC_GROUND classes monitor airbase traffic and regulate speed while taxiing.

    @@ -261,12 +278,6 @@ even when there are hardly any players in the mission.

    Airbase

    Wrapper -- AIRBASE is a wrapper class to handle the DCS Airbase objects.

    - - - - AirbasePolice - -

    Functional -- This module monitors airbases traffic.

    @@ -290,7 +301,7 @@ even when there are hardly any players in the mission.

    CleanUp -

    Functional -- The CLEANUP class keeps an area clean of crashing or colliding airplanes.

    +

    Functional -- The CLEANUP_AIRBASE class keeps an area clean of crashing or colliding airplanes.

    @@ -442,6 +453,12 @@ even when there are hardly any players in the mission.

    Core -- The FSM (Finite State Machine) class and derived FSM_ classes are design patterns allowing efficient (long-lasting) processes and workflows.

    + + + + Goal + +

    Core (WIP) -- Base class to allow the modeling of processes to achieve Goals.

    @@ -514,12 +531,26 @@ are design patterns allowing efficient (long-lasting) processes and workflows.Process_Pickup + + + + Protect + +

    Functional -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.

    Radio

    Core -- The RADIO Module is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions...

    + + + + Rat + +
      +
    • Functional - Create random airtraffic in your missions.
    • +
    @@ -587,7 +618,7 @@ and creates a CSV file logging the scoring events and results for use at team or Spawn -

    Functional -- Spawn dynamically new GROUPs in your missions.

    +

    Core -- SPAWN class dynamically spawns new groups of units in your missions.

    @@ -618,6 +649,12 @@ and creates a CSV file logging the scoring events and results for use at team or Task

    Tasking -- This module contains the TASK class, the main engine to run human taskings.

    + + + + TaskZoneCapture + +

    Tasking - The TASK_Protect models tasks for players to protect or capture specific zones.

    @@ -660,6 +697,18 @@ and creates a CSV file logging the scoring events and results for use at team or Unit

    Wrapper - UNIT is a wrapper class for the DCS Class Unit.

    + + + + UserFlag + +

    Core (WIP) -- Manage user flags.

    + + + + UserSound + +

    Core (WIP) -- Manage user sound.

    @@ -667,12 +716,42 @@ and creates a CSV file logging the scoring events and results for use at team or

    This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment!.

    + + + + Velocity + +

    Core -- VELOCITY models a speed, which can be expressed in various formats according the Settings.

    Zone

    Core -- ZONE classes define zones within your mission of various forms, with various capabilities.

    + + + + ZoneCaptureCoalition + +

    Functional -- (WIP R2.3) Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.

    + + + + ZoneGoal + +

    Functional (WIP) -- Base class that models processes to achieve goals involving a Zone.

    + + + + ZoneGoalCargo + +

    Functional (WIP) -- Base class that models processes to achieve goals involving a Zone and Cargo.

    + + + + ZoneGoalCoalition + +

    Functional (WIP) -- Base class that models processes to achieve goals involving a Zone for a Coalition.

    diff --git a/docs/Documentation/land.html b/docs/Documentation/land.html index e4a3153a4..1369d87b8 100644 --- a/docs/Documentation/land.html +++ b/docs/Documentation/land.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Documentation/routines.html b/docs/Documentation/routines.html index 18dfebc72..ccea4da2e 100644 --- a/docs/Documentation/routines.html +++ b/docs/Documentation/routines.html @@ -28,9 +28,9 @@
  • AI_Cas
  • AI_Formation
  • AI_Patrol
  • +
  • ATC_Ground
  • Account
  • Airbase
  • -
  • AirbasePolice
  • Assign
  • Base
  • Cargo
  • @@ -60,6 +60,7 @@
  • Escort
  • Event
  • Fsm
  • +
  • Goal
  • Group
  • Identifiable
  • Menu
  • @@ -72,7 +73,9 @@
  • Positionable
  • Process_JTAC
  • Process_Pickup
  • +
  • Protect
  • Radio
  • +
  • Rat
  • Route
  • Scenery
  • ScheduleDispatcher
  • @@ -88,6 +91,7 @@
  • Static
  • StaticObject
  • Task
  • +
  • TaskZoneCapture
  • Task_A2A
  • Task_A2A_Dispatcher
  • Task_A2G
  • @@ -95,8 +99,15 @@
  • Task_Cargo
  • Task_PICKUP
  • Unit
  • +
  • UserFlag
  • +
  • UserSound
  • Utils
  • +
  • Velocity
  • Zone
  • +
  • ZoneCaptureCoalition
  • +
  • ZoneGoal
  • +
  • ZoneGoalCargo
  • +
  • ZoneGoalCoalition
  • env
  • land
  • routines
  • diff --git a/docs/Presentations/AIRBASEPOLICE/Dia1.JPG b/docs/Presentations/AIRBASEPOLICE/Dia1.JPG new file mode 100644 index 000000000..80970f0f2 Binary files /dev/null and b/docs/Presentations/AIRBASEPOLICE/Dia1.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_1.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_1.JPG new file mode 100644 index 000000000..bb638ea9b Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_1.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_10.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_10.JPG new file mode 100644 index 000000000..31aaf520d Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_10.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_11.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_11.JPG new file mode 100644 index 000000000..b27640e76 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_11.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_2.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_2.JPG new file mode 100644 index 000000000..db129826b Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_2.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_3.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_3.JPG new file mode 100644 index 000000000..ea4f6beaa Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_3.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_4.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_4.JPG new file mode 100644 index 000000000..e0b1db230 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_4.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_5.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_5.JPG new file mode 100644 index 000000000..749b96e85 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_5.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_6.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_6.JPG new file mode 100644 index 000000000..df6b1f564 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_6.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_7.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_7.JPG new file mode 100644 index 000000000..b6765497e Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_7.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_8.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_8.JPG new file mode 100644 index 000000000..513dbc503 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_8.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_9.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_9.JPG new file mode 100644 index 000000000..f012cb431 Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_DISPATCHER-ME_9.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_1.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_1.JPG new file mode 100644 index 000000000..cb043205f Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_1.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_2.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_2.JPG new file mode 100644 index 000000000..dea32f3dd Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_2.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_3.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_3.JPG new file mode 100644 index 000000000..4be3b4dbc Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_3.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_4.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_4.JPG new file mode 100644 index 000000000..cb043205f Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_4.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_5.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_5.JPG new file mode 100644 index 000000000..12dcb59da Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_5.JPG differ diff --git a/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_6.JPG b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_6.JPG new file mode 100644 index 000000000..87d52dbce Binary files /dev/null and b/docs/Presentations/AI_A2A_DISPATCHER/AI_A2A_GCICAP-ME_6.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia1.JPG b/docs/Presentations/ATC_GROUND/Dia1.JPG new file mode 100644 index 000000000..c0cecdfed Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia1.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia2.JPG b/docs/Presentations/ATC_GROUND/Dia2.JPG new file mode 100644 index 000000000..3b71efc15 Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia2.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia3.JPG b/docs/Presentations/ATC_GROUND/Dia3.JPG new file mode 100644 index 000000000..1b3ff5a76 Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia3.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia4.JPG b/docs/Presentations/ATC_GROUND/Dia4.JPG new file mode 100644 index 000000000..a2af363ad Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia4.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia5.JPG b/docs/Presentations/ATC_GROUND/Dia5.JPG new file mode 100644 index 000000000..9a76bf5c8 Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia5.JPG differ diff --git a/docs/Presentations/ATC_GROUND/Dia6.JPG b/docs/Presentations/ATC_GROUND/Dia6.JPG new file mode 100644 index 000000000..46f65218d Binary files /dev/null and b/docs/Presentations/ATC_GROUND/Dia6.JPG differ diff --git a/docs/Presentations/CLEANUP_AIRBASE/Dia1.JPG b/docs/Presentations/CLEANUP_AIRBASE/Dia1.JPG new file mode 100644 index 000000000..b5d0e85b8 Binary files /dev/null and b/docs/Presentations/CLEANUP_AIRBASE/Dia1.JPG differ diff --git a/docs/Presentations/RAT/RAT.png b/docs/Presentations/RAT/RAT.png new file mode 100644 index 000000000..1cfd25ca1 Binary files /dev/null and b/docs/Presentations/RAT/RAT.png differ diff --git a/docs/Presentations/RAT/RAT_Airport_Selection.png b/docs/Presentations/RAT/RAT_Airport_Selection.png new file mode 100644 index 000000000..505a53353 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Airport_Selection.png differ diff --git a/docs/Presentations/RAT/RAT_Basic_Lua_Script.png b/docs/Presentations/RAT/RAT_Basic_Lua_Script.png new file mode 100644 index 000000000..6aacf0afb Binary files /dev/null and b/docs/Presentations/RAT/RAT_Basic_Lua_Script.png differ diff --git a/docs/Presentations/RAT/RAT_Examples_Misc.png b/docs/Presentations/RAT/RAT_Examples_Misc.png new file mode 100644 index 000000000..357194eeb Binary files /dev/null and b/docs/Presentations/RAT/RAT_Examples_Misc.png differ diff --git a/docs/Presentations/RAT/RAT_Examples_Spawn_at_Zones.png b/docs/Presentations/RAT/RAT_Examples_Spawn_at_Zones.png new file mode 100644 index 000000000..95ce76ce6 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Examples_Spawn_at_Zones.png differ diff --git a/docs/Presentations/RAT/RAT_Examples_Spawn_in_Air.png b/docs/Presentations/RAT/RAT_Examples_Spawn_in_Air.png new file mode 100644 index 000000000..9096525f3 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Examples_Spawn_in_Air.png differ diff --git a/docs/Presentations/RAT/RAT_Examples_Specify_Departure_and_Destination.png b/docs/Presentations/RAT/RAT_Examples_Specify_Departure_and_Destination.png new file mode 100644 index 000000000..f27339fe6 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Examples_Specify_Departure_and_Destination.png differ diff --git a/docs/Presentations/RAT/RAT_Flight_Plan.png b/docs/Presentations/RAT/RAT_Flight_Plan.png new file mode 100644 index 000000000..159c53a23 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Flight_Plan.png differ diff --git a/docs/Presentations/RAT/RAT_Gaussian.png b/docs/Presentations/RAT/RAT_Gaussian.png new file mode 100644 index 000000000..c67b1cadd Binary files /dev/null and b/docs/Presentations/RAT/RAT_Gaussian.png differ diff --git a/docs/Presentations/RAT/RAT_Mission_Setup.png b/docs/Presentations/RAT/RAT_Mission_Setup.png new file mode 100644 index 000000000..b30dadb13 Binary files /dev/null and b/docs/Presentations/RAT/RAT_Mission_Setup.png differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia1.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia1.JPG new file mode 100644 index 000000000..6be504e18 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia1.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia10.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia10.JPG new file mode 100644 index 000000000..0ec74665a Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia10.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia11.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia11.JPG new file mode 100644 index 000000000..ab024e79e Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia11.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia2.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia2.JPG new file mode 100644 index 000000000..1cedf0488 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia2.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia3.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia3.JPG new file mode 100644 index 000000000..9575ddf60 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia3.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia4.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia4.JPG new file mode 100644 index 000000000..5527c3c91 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia4.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia5.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia5.JPG new file mode 100644 index 000000000..e034e3b6e Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia5.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia6.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia6.JPG new file mode 100644 index 000000000..6ec63947d Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia6.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia7.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia7.JPG new file mode 100644 index 000000000..4a81a7d77 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia7.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia8.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia8.JPG new file mode 100644 index 000000000..ec2b4d47a Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia8.JPG differ diff --git a/docs/Presentations/TASK_A2G_DISPATCHER/Dia9.JPG b/docs/Presentations/TASK_A2G_DISPATCHER/Dia9.JPG new file mode 100644 index 000000000..40d612b50 Binary files /dev/null and b/docs/Presentations/TASK_A2G_DISPATCHER/Dia9.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia1.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia1.JPG new file mode 100644 index 000000000..87777ced7 Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia1.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia2.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia2.JPG new file mode 100644 index 000000000..602372ec3 Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia2.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia3.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia3.JPG new file mode 100644 index 000000000..dc3c953ac Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia3.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia4.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia4.JPG new file mode 100644 index 000000000..6edd323eb Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia4.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia5.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia5.JPG new file mode 100644 index 000000000..88435fbbe Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia5.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia6.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia6.JPG new file mode 100644 index 000000000..90d60e99c Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia6.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia7.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia7.JPG new file mode 100644 index 000000000..b332cd3e4 Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia7.JPG differ diff --git a/docs/Presentations/ZONE_CAPTURE_COALITION/Dia8.JPG b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia8.JPG new file mode 100644 index 000000000..019cef4db Binary files /dev/null and b/docs/Presentations/ZONE_CAPTURE_COALITION/Dia8.JPG differ diff --git a/docs/README.md b/docs/README.md index 2ecfc8809..3cc335f97 100644 --- a/docs/README.md +++ b/docs/README.md @@ -81,25 +81,25 @@ Each TASK has a TASK ACTION flow, which is the flow that a player (hosted by a U MOOSE AI Controlling Classes provide mechanisms to control AI over long lasting processes. These AI Controlling Classes are based on FSM (Finite State Machine) Classes, and provided an encapsulated way to make AI behave or execute an activity. -* [AI_A2A_DISPATCHER](Documentation/AI_A2A_Dispatcher.html): Create an automatic and dynamic A2A defense system, executed by AI units, as a result of detected A2A airborne targets executing CAP and GCI. This script replaces the GCICAP script. +* [AI A2A Defenses](Documentation/AI_A2A_Dispatcher.html): Create automatic A2A defense systems executed by AI and perform CAP or GCI. + * [AI\_A2A\_GCICAP](Documentation/AI_A2A_Dispatcher.html#AI_A2A_GCICAP): Using an easy process you can define an A2A defense system using the Mission Editor. + * [AI\_A2A\_DISPATCHER](Documentation/AI_A2A_Dispatcher.html#AI_A2A_DISPATCHER): Same as AI\_A2A\_GCICAP, but is for more advanced or developer type mission designers. This class provides more options. -* [AI_BALANCER](Documentation/AI_Balancer.html): Compensate in a multi player mission the abscence of players with dynamically spawned AI air units. When players join CLIENTS, the AI will either be destroyed, or will fly back to the home or nearest friendly airbase. +* [AI\_BALANCER](Documentation/AI_Balancer.html): Compensate in a multi player mission the abscence of players with dynamically spawned AI air units. When players join CLIENTS, the AI will either be destroyed, or will fly back to the home or nearest friendly airbase. -* [AI_PATROL_ZONE](Documentation/AI_Patrol_Zone.html): Make an alive AI Group patrol a zone derived from the ZONE_BASE class. Manage out-of-fuel events and set altitude and speed ranges for the patrol. +* [AI\_PATROL\_ZONE](Documentation/AI_Patrol_Zone.html): Make an alive AI Group patrol a zone derived from the ZONE_BASE class. Manage out-of-fuel events and set altitude and speed ranges for the patrol. -* [AI_CAP](Documentation/AI_Cap.html): Make an alive AI Group perform Combat Air Patrol as a dynamic process. +* [AI\_CAP](Documentation/AI_Cap.html): Make an alive AI Group perform Combat Air Patrol as a dynamic process. -* [AI_CAS](Documentation/AI_Cas.html): Make an alive AI Group perform Close Air Support as a dynamic process. +* [AI\_CAS](Documentation/AI_Cas.html): Make an alive AI Group perform Close Air Support as a dynamic process. -* [AI_BAI](Documentation/AI_Bai.html): Make an alive AI Group perform Battlefield Air Interdiction as a dynamic process. +* [AI\_BAI](Documentation/AI_Bai.html): Make an alive AI Group perform Battlefield Air Interdiction as a dynamic process. ## 2.3. MOOSE Functional Classes MOOSE Functional Classes provide various functions that are useful in mission design. -* [SPAWN](Documentation/Spawn.html): Spawn new groups (and units) during mission execution. - * [ESCORT](Documentation/Escort.html): Makes groups consisting of helicopters, airplanes, ground troops or ships within a mission joining your flight. You can control these groups through the ratio menu during your flight. Available commands are around: Navigation, Position Hold, Reporting (Target Detection), Attacking, Assisted Attacks, ROE, Evasion, Mission Execution and more ... * [MISSILETRAINER](Documentation/MissileTrainer.html): Missile trainer, it destroys missiles when they are within a certain range of player airplanes, displays tracking and alert messages of missile launches; approach; destruction, and configure with radio menu commands. Various APIs available to configure the trainer. @@ -114,7 +114,16 @@ MOOSE Functional Classes provide various functions that are useful in mission de * [AIRBASEPOLICE](Documentation/AirbasePolice.html): Control the speed of players at the airbases. Speeding players are eliminated (does not work due to a bug in the DCS). -* [CLEANUP](Documentation/Cleanup.html): Keeps the airbases clean from clutter. (Only partly functional due to a bug in DCS, destroyed objects cannot be removed). +* [CLEANUP\_AIRBASE](Documentation/CleanUp.html): Keeps the airbases clean from clutter. (Only partly functional due to a bug in DCS, destroyed objects cannot be removed). + +* [RAT](Documentation/Rat.html): Random Air Traffic engine developed by FunkyFranky, use the available airspace! + +* [ATC_GROUND](Documentation/ATC_Ground.html): Monitor players on the airbase and make them behave and provide instructions. + * [ATC_GROUND](Documentation/ATC_Ground.html#ATC_GROUND_CAUCASUS): ATC Ground operations for Caucasus. + * [ATC_GROUND](Documentation/ATC_Ground.html#ATC_GROUND_NEVADA): ATC Ground operations for Nevada. + * [ATC_GROUND](Documentation/ATC_Ground.html#ATC_GROUND_NORMANDY): ATC Ground operations for Normandy. + +* [ZONE\_CAPTURE\_COALITION](Documentation/ZoneCaptureCoalition.html): Create a zone capturing process around a zone! Zones can be guarded, attacked, empty or captured. ## 2.4. MOOSE Wrapper Classes @@ -153,28 +162,46 @@ These classes define the base building blocks of the MOOSE framework. These clas * [SCHEDULER](Documentation/Scheduler.html): This class implements a timer scheduler that will call at optional specified intervals repeatedly or just one time a scheduled function. -* [FSM](Documentation/Fsm.html): The main FSM class can be used to build a Finite State Machine. The derived FSM_ classes provide Finite State Machine building capability for CONTROLLABLEs, ACT_ (Task Actions) classes, TASKs and SETs. +* [SPAWN](Documentation/Spawn.html#SPAWN): Spawn new groups (and units) during mission execution. -* [MENU](Documentation/Menu.html): Set Menu options (F10) for All Players, Coalitions, Groups, Clients. MENU also manages the recursive removal of menus, which is a big asset! +* [SPAWNSTATIC](Documentation/SpawnStatic.html#SPAWNSTATIC): Spawn Static objects using a predefined "template". -* [SET](Documentation/Set.html): Create SETs of MOOSE objects. SETs can be created for GROUPs, UNITs, AIRBASEs, ... -The SET can be filtered with defined filter criteria. -Iterators are available that iterate through the GROUPSET, calling a function for each object within the SET. +* [Finite State Machines](Documentation/Fsm.html): Finite State Machine provides a process management or state machine capability for various scenarios. +* [FSM](Documentation/Fsm.html#FSM): The main FSM class can be used to build a generic Finite State Machine. +* [FSM\_CONTROLLABLE](Documentation/Fsm.html#FSM_CONTROLLABLE): An FSM class to control a Controllable. A controllable is a group or unit that can be controlled. + +* [MENU](Documentation/Menu.html): Set Menu options under the radio menu (F10). MENU classes also manage the recursive removal of menus, and the intelligent refresh of menu options (only the changes are applied). +* [MENU\_MISSION](Documentation/Menu.html#MENU_MISSION): Set a main menu structure for the complete mission, for all players. +* [MENU\_MISSION\_COMMAND](Documentation/Menu.html#MENU_MISSION_COMMAND): Set a main menu command for the complete mission, for all players. +* [MENU\_COALITION](Documentation/Menu.html#MENU_COALITION): Set a menu structure for a coalition, for all players of that coalition. +* [MENU\_COALITION\_COMMAND](Documentation/Menu.html#MENU_COALITION_COMMAND): Set a menu command for a coalition, for all players of that coalition. +* [MENU\_GROUP](Documentation/Menu.html#MENU_GROUP): Set a menu structure for a group, for all players of that group. +* [MENU\_GROUP\_COMMAND](Documentation/Menu.html#MENU_GROUP_COMMAND): Set a menu command for a group, for all players of that group. + +* [SET](Documentation/Set.html): Create SETs of MOOSE objects. The SET can be filtered with defined filter criteria. Iterators are available that iterate through the SET, calling a function for each object within the SET. +* [SET\_GROUP](Documentation/Set.html#SET_GROUP): Create a SET of GROUP objects. +* [SET\_UNIT](Documentation/Set.html#SET_UNIT): Create a SET of UNIT objects. +* [SET\_CLIENT](Documentation/Set.html#SET_CLIENT): Create a SET of CLIENT objects. +* [SET\_AIRBASE](Documentation/Set.html#SET_AIRBASE): Create a SET of AIRBASE objects. +* [SET\_CARGO](Documentation/Set.html#SET_CARGO): Create a SET of CARGO objects. * [MESSAGE](Documentation/Message.html): A message publishing system, displaying messages to Clients, Coalitions or All players. -* [POINTS](Documentation/Point.html): A set of point classes to manage the 2D or 3D simulation space, through an extensive method library. -The POINT_VEC3 class manages the 3D simulation space, while the POINT_VEC2 class manages the 2D simulation space. +* [COORDINATE](Documentation/Point.html#COORDINATE): Manage 2D and 3D points in the simulation space, and use its methods for various actions on the coordinate. +* [POINT\_VEC2](Documentation/Point.html#POINT_VEC2): Manage 2D points in the simulation space. +* [POINT\_VEC3](Documentation/Point.html#POINT_VEC3): Manage 3D points in the simulation space. -* [ZONES](Documentation/Zone.html): A set of zone classes that provide the functionality to validate the presence of GROUPS, UNITS, CLIENTS, STATICS within a certain ZONE. The zones can take various forms and can be movable. +* [ZONES](Documentation/Zone.html#ZONE): Create a zone object from a trigger zone as defined in the Mission Editor. +* [ZONE\_POLYGON](Documentation/Zone.html#ZONE_POLYGON): Create a zone object from a group object, which is late activated, and its route points define the zone. +* [ZONE\_RADIUS](Documentation/Zone.html#ZONE_RADIUS): Create a zone object from a 2D vector on the battlefield, with a defined radius. +* [ZONE\_UNIT](Documentation/Zone.html#ZONE_UNIT): Create a zone object around a unit on the battlefield, with a defined radius. This, this zone is a moving zone! +* [ZONE\_GROUP](Documentation/Zone.html#ZONE_GROUP): Create a zone object around a group on the battlefield, with a defined radius. The first object in the group has the zone, and is thus a moving zone! -* [CARGO](Documentation/Cargo.html): Manage Cargo in the simulation. +* [CARGO](Documentation/Cargo.html): Manage Cargo in the simulation. +* [CARGO\_GROUP](Documentation/Cargo.html#CARGO_GROUP): Manage Cargo that is defined as a GROUP object within the simulation. -* [SPAWNSTATIC](Documentation/SpawnStatic.html): Spawn dynamically static objects. - -* [BEACON](Documentation/Radio.html): Create beacons. - -* [RADIO](Documentation/Radio.html): Create radio communication. +* [BEACON](Documentation/Radio.html): Create beacons. +* [RADIO](Documentation/Radio.html): Create radio communication. diff --git a/docs/Usage_Guide.md b/docs/Usage_Guide.md index 5e936f4f3..dba4e3368 100644 --- a/docs/Usage_Guide.md +++ b/docs/Usage_Guide.md @@ -11,15 +11,21 @@ It is free for download and usage, since it is released under the GNU 3.0 open s Although the MOOSE contributors and tester are using the GitHub service to enforces a structured approval process, release and change management, and a communicative distribution and deployment, you, as a mission designer, don't need to mess with it. Still, if you are interrested intesting the latest features of MOOSE of in adding your own, you can read the [relevant](http://flightcontrol-master.github.io/MOOSE/Beta_Test_Guide.html) [guides](http://flightcontrol-master.github.io/MOOSE/Contribution_Guide.html) and/or contact FlightControl The MOOSE framework development is an open source project, and as such, contributors are welcome and encouraged to contribute on the development.Some key users have already started with this process. +## 1.2) MOOSE using a normal editor + +You can use the MOOSE framework with a normal editor. This is perfectly okay. +But you won't have IntelliSense enabled, which allows you to quickly search for the correct methods per class. ## 1.2) Eclipse LDT -MOOSE utilizes the Eclipse Lua Development Tools. As a result, the MOOSE framework is documented using the luadocumentor standard. +The LDT environment or "Eclipse Lua Development Tools" is a fully integrated development environment for LUA developers. +It is recommended to use the LDT as your mission design editor using MOOSE. The MOOSE framework is documented using the luadocumentor standard. Every class, method and variable is documented within the source, and mission designers can write mission script lua code that is **intellisense**(-ed) ... What that means is that while you are coding your mission, your object and variables (derived from MOOSE classes) will list the methods and properties of that class interactively while coding ... ![Intellisense](Usage/Intellisense.JPG) +This highly increases the quality and the speed of your scripting. ## 1.3) LUA training