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/Moose/Core/Cargo.lua b/Moose Development/Moose/Core/Cargo.lua index 6ec648cd8..3d7d4ec14 100644 --- a/Moose Development/Moose/Core/Cargo.lua +++ b/Moose Development/Moose/Core/Cargo.lua @@ -529,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" @@ -549,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 @@ -678,7 +690,7 @@ 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 @@ -695,346 +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 --- ) - - self:SetEventPriority( 5 ) - - 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. - self:F( { CargoName = self:GetName() } ) - _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:WaypointGround( Speed ) - - Points[#Points+1] = ToPointVec2:WaypointGround( 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 CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or POINT_VEC2: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: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: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 + 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:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( 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 diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 218384985..bf8ef630e 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -522,7 +522,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 @@ -649,6 +649,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 diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 2bc29f6c5..533c51e1f 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -157,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/Functional/AirbasePolice.lua b/Moose Development/Moose/Functional/AirbasePolice.lua index ac7d1d0ba..c5b0c0d30 100644 --- a/Moose Development/Moose/Functional/AirbasePolice.lua +++ b/Moose Development/Moose/Functional/AirbasePolice.lua @@ -108,6 +108,8 @@ function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, {}, 0, 2, 0.05 ) + + trigger.action.setUserFlag("SSB",100) return self end @@ -181,22 +183,9 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() 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() .. " is being damaged at the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() + MESSAGE:New( "Player " .. Client:GetPlayerName() .. " is being kicked from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() --- @param Wrapper.Client#CLIENT Client - local function DestroyUntilHeavilyDamaged( Client ) - local ClientCoord = Client:GetCoordinate() - ClientCoord:Explosion( 100 ) - local Damage = Client:GetLife() - local InitialLife = Client:GetLife0() - MESSAGE:New( "Player " .. Client:GetPlayerName() .. " Damage ... " .. Damage, 5, "Airbase Police" ):ToAll() - if ( Damage / InitialLife ) * 100 < 80 then - Client:ScheduleStop( DestroyUntilHeavilyDamaged ) - end - end - Client:ScheduleOnce( 1, DestroyUntilHeavilyDamaged, Client ) - --Client:ScheduleRepeat( 1, 1, 0, nil, DestroyUntilHeavilyDamaged, Client ) - --Client:Destroy() - trigger.action.setUserFlag( "AIRCRAFT_"..Client:GetID(), 100) + Client:Destroy() Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0 ) 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/Tasking/TaskZoneCapture.lua b/Moose Development/Moose/Tasking/TaskZoneCapture.lua index b23f35988..fe8afcb5e 100644 --- a/Moose Development/Moose/Tasking/TaskZoneCapture.lua +++ b/Moose Development/Moose/Tasking/TaskZoneCapture.lua @@ -206,6 +206,7 @@ do -- TASK_ZONE_CAPTURE ClassName = "TASK_ZONE_CAPTURE", } + --- Instantiates a new TASK_ZONE_CAPTURE. -- @param #TASK_ZONE_CAPTURE self -- @param Tasking.Mission#MISSION Mission @@ -236,6 +237,7 @@ do -- TASK_ZONE_CAPTURE return self end + --- Instantiates a new TASK_ZONE_CAPTURE. -- @param #TASK_ZONE_CAPTURE self function TASK_ZONE_CAPTURE:UpdateTaskInfo() @@ -247,6 +249,7 @@ do -- TASK_ZONE_CAPTURE 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 diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 59da812ae..c4074bb7c 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -224,6 +224,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 +232,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 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/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 7f9a34ed2..ec976b0bc 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -159,6 +159,24 @@ 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 + USERFLAG:New( self:GetGroup():GetName() ):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: