diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 04da94f98..c6c422798 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -27,7 +27,7 @@ -- * 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 +-- The RAT class creates an entry in the F10 radio 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), @@ -49,8 +49,7 @@ -- -- # YouTube Channel -- --- ### RAT videos are work in progress. --- ### [MOOSE YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) +-- ### [DCS WORLD - MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO) -- -- === -- @@ -123,7 +122,9 @@ -- @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 #boolean respawn_after_crash Aircraft will be respawned after a crash, e.g. when they get shot down. +-- @field #boolean respawn_inair Aircraft are allowed to spawned in air if they cannot be respawned on ground because there is not free parking spot. Default is true. +-- @field #number respawn_delay Delay in seconds until a repawn happens. -- @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. @@ -141,10 +142,13 @@ -- @field #number activate_delay Delay in seconds before first uncontrolled group is activated. Default is 5 seconds. -- @field #number activate_delta Time interval in seconds between activation of uncontrolled groups. Default is 5 seconds. -- @field #number activate_frand Randomization factor of time interval (activate_delta) between activating uncontrolled groups. Default is 0. --- @field #number activate_max=0 Maximal number of uncontrolle aircraft, which will be activated at a time. Default is 0 +-- @field #number activate_max Maximum number of uncontrolled aircraft, which will be activated at the same time. Default is 1. -- @field #string onboardnum Sets the onboard number prefix. Same as setting "TAIL #" in the mission editor. --- @field #number onboardnum0 (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is one. +-- @field #number onboardnum0 (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 1. +-- @field #boolean checkonrunway Aircraft are checked if they were accidentally spawned on the runway. Default is true. +-- @field #boolean checkontop Aircraft are checked if they were accidentally spawned on top of another unit. Default is true. -- @field #number rbug_maxretry Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. Default is 3. +-- @field #boolean useparkingdb Parking spots are added to data base once an aircraft has used it. These spots can later be used by other aircraft. Default is true. -- @extends Core.Spawn#SPAWN ---# RAT class, extends @{Spawn#SPAWN} @@ -352,6 +356,8 @@ RAT={ 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_after_crash=true, -- Aircraft will be respawned after a crash. + respawn_inair=true, -- Aircraft are spawned in air if there is no free parking spot on the ground. respawn_delay=nil, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. waypointdescriptions={}, -- Array with descriptions for waypoint markers. @@ -371,10 +377,13 @@ RAT={ activate_delay=5, -- Delay in seconds before first uncontrolled group is activated. activate_delta=5, -- Time interval in seconds between activation of uncontrolled groups. activate_frand=0, -- Randomization factor of time interval (activate_delta) between activating uncontrolled groups. - activate_max=0, -- Max number of uncontrolle aircraft, which will be activated at a time. + activate_max=1, -- Max number of uncontrolle aircraft, which will be activated at a time. onboardnum=nil, -- Tail number. onboardnum0=1, -- (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is one. rbug_maxretry=3, -- Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. + checkonrunway=true, -- Check whether aircraft have been spawned on the runway. + checkontop=true, -- Check whether aircraft have been spawned on top of another unit. + useparkingdb=true, -- Put known parking spots into a data base. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -486,6 +495,10 @@ RAT.markerid=0 -- @field #string MenuF10 RAT.MenuF10=nil +--- RAT parking spots data base. +-- @list parking +RAT.parking={} + --- Some ID to identify who we are in output of the DCS.log file. -- @field #string id RAT.id="RAT | " @@ -493,10 +506,11 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.2.0", + version = "2.2.1", print = true, } + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --TODO list: @@ -689,12 +703,16 @@ function RAT:Spawn(naircraft) text=text..string.format("Return Zone: %s\n", tostring(self.returnzone)) 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 delay: %s\n", tostring(self.respawn_delay)) + text=text..string.format("Respawn off: %s\n", tostring(self.norespawn)) 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("Respawn after crash: %s\n", tostring(self.respawn_after_crash)) + text=text..string.format("Respawn in air: %s\n", tostring(self.respawn_inair)) text=text..string.format("ROE: %s\n", tostring(self.roe)) text=text..string.format("ROT: %s\n", tostring(self.rot)) + text=text..string.format("Immortal: %s\n", tostring(self.immortal)) + text=text..string.format("Invisible: %s\n", tostring(self.invisible)) 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)) @@ -713,12 +731,16 @@ function RAT:Spawn(naircraft) text=text..string.format("Radio frequency : %s\n", tostring(self.frequency)) text=text..string.format("Radio modulation : %s\n", tostring(self.frequency)) text=text..string.format("Tail # prefix : %s\n", tostring(self.onboardnum)) + text=text..string.format("Check on runway: %s\n", tostring(self.checkonrunway)) + text=text..string.format("Check on top: %s\n", tostring(self.checkontop)) + text=text..string.format("Max respawn attempts: %s\n", tostring(self.rbug_maxretry)) + text=text..string.format("Parking DB: %s\n", tostring(self.useparkingdb)) text=text..string.format("Uncontrolled: %s\n", tostring(self.uncontrolled)) if self.uncontrolled and self.activate_uncontrolled then + text=text..string.format("Uncontrolled max : %4.1f\n", self.activate_max) text=text..string.format("Uncontrolled delay: %4.1f\n", self.activate_delay) text=text..string.format("Uncontrolled delta: %4.1f\n", self.activate_delta) text=text..string.format("Uncontrolled frand: %4.1f\n", self.activate_frand) - text=text..string.format("Uncontrolled max : %4.1f\n", self.activate_max) end if self.livery then text=text..string.format("Available liveries:\n") @@ -752,142 +774,34 @@ function RAT:Spawn(naircraft) -- Handle events. self:HandleEvent(EVENTS.Birth, self._OnBirth) - self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup) + self:HandleEvent(EVENTS.EngineStartup, self._OnEngineStartup) 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? + self:HandleEvent(EVENTS.Dead, self._OnDeadOrCrash) + self:HandleEvent(EVENTS.Crash, self._OnDeadOrCrash) + self:HandleEvent(EVENTS.Hit, self._OnHit) + -- No groups should be spawned. if self.ngroups==0 then return nil - elseif self.uncontrolled then - for i=1,self.ngroups do - self:_SpawnWithRoute() - end - if self.activate_uncontrolled then - SCHEDULER:New(nil, self._ActivateUncontrolled, {self}, self.activate_delay, self.activate_delta, self.activate_frand) - end - else - SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.0, Tstop) end + -- Start scheduled spawning. + SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.0, Tstop) + + -- Start scheduled activation of uncontrolled groups. + if self.uncontrolled and self.activate_uncontrolled then + SCHEDULER:New(nil, self._ActivateUncontrolled, {self}, self.activate_delay, self.activate_delta, self.activate_frand) + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Randomly activates an uncontrolled aircraft. --- @param #RAT self -function RAT:_ActivateUncontrolled() - if self.Debug then - env.info(RAT.id.."_ActivateUncontrolled") - end - - -- Spawn indices of uncontrolled inactive aircraft. - local idx={} - local rat={} - - -- Number of active aircraft. - local nactive=0 - - -- Loop over RAT groups and count the active ones. - for spawnindex,ratcraft in pairs(self.ratcraft) do - - local group=ratcraft.group --Wrapper.Group#GROUP - - if group and group:IsAlive() then - - if self.Debug then - local text=string.format("Spawnindex = %d, group name = %s, active = %s", spawnindex, ratcraft.group:GetName(), tostring(ratcraft.active)) - env.info(RAT.id..text) - end - - if ratcraft.active then - nactive=nactive+1 - else - table.insert(idx, spawnindex) - end - - end - end - - if self.Debug then - local text=string.format("Nactive = %d, Ninactive = %d, max active=%d", nactive, #idx, self.activate_max) - env.info(RAT.id..text) - end - - if #idx>0 and nactive001, 002, ... -- @param #number zero (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 0. function RAT:SetOnboardNum(tailnumprefix, zero) + self:F2({tailnumprefix=tailnumprefix, zero=zero}) self.onboardnum=tailnumprefix if zero ~= nil then self.onboardnum0=zero @@ -1610,6 +1654,7 @@ end -- @param #RAT self -- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor. function RAT:_InitAircraft(DCSgroup) + self:F2(DCSgroup) local DCSunit=DCSgroup:getUnit(1) local DCSdesc=DCSunit:getDesc() @@ -1647,6 +1692,15 @@ function RAT:_InitAircraft(DCSgroup) -- service ceiling in meters self.aircraft.ceiling=DCSdesc.Hmax + -- Store all descriptors. + --self.aircraft.descriptors=DCSdesc + + -- aircraft dimensions + self.aircraft.length=DCSdesc.box.max.x + self.aircraft.height=DCSdesc.box.max.y + self.aircraft.width=DCSdesc.box.max.z + self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) + -- info message local text=string.format("\n******************************************************\n") text=text..string.format("Aircraft parameters:\n") @@ -1654,6 +1708,9 @@ function RAT:_InitAircraft(DCSgroup) 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("Length (x) = %6.1f m\n", self.aircraft.length) + text=text..string.format("Width (z) = %6.1f m\n", self.aircraft.width) + text=text..string.format("Height (y) = %6.1f m\n", self.aircraft.height) 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) @@ -1702,19 +1759,13 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live local temp={RAT.wp.cold, RAT.wp.hot} takeoff=temp[math.random(2)] end - + -- Number of respawn attempts after spawning on runway. local nrespawn=0 if _nrespawn then nrespawn=_nrespawn end - -- Spawn position. - local lastpos=nil - if _lastpos then - lastpos=_lastpos - end - -- Set flight plan. local departure, destination, waypoints, WPholding, WPfinal = self:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) @@ -1722,6 +1773,12 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live if not (departure and destination and waypoints) then return nil end + + -- Find parking spot in RAT parking DB. Category 4 should be airports and farps. Ships would be caterory 1. + local _spawnpos=_lastpos + if self.useparkingdb and (takeoff==RAT.wp.cold or takeoff==RAT.wp.hot) and departure:GetCategory()==4 and _spawnpos==nil then + _spawnpos=self:_FindParkingSpot(departure) + end -- Set (another) livery. local livery @@ -1738,11 +1795,15 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live end -- Modify the spawn template to follow the flight plan. - self:_ModifySpawnTemplate(waypoints, livery, lastpos) + self:_ModifySpawnTemplate(waypoints, livery, _spawnpos) -- Actually spawn the group. local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP + -- Increase counter of alive groups (also uncontrolled ones). + self.alive=self.alive+1 + self:T(RAT.id..string.format("Alive groups counter now = %d.",self.alive)) + -- ATC is monitoring this flight (if it is supposed to land). if self.ATCswitch and landing==RAT.wp.landing then if self.returnzone then @@ -1780,6 +1841,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live self.ratcraft[self.SpawnIndex]["departure"]=departure self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir() + self.ratcraft[self.SpawnIndex]["nunits"]=group:GetInitialSize() -- Time and position on ground. For check if aircraft is stuck somewhere. if group:InAir() then self.ratcraft[self.SpawnIndex]["Tground"]=nil @@ -1815,6 +1877,12 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live -- Number of preformed spawn attempts for this group. self.ratcraft[self.SpawnIndex].nrespawn=nrespawn + + -- If we start at a parking position, we memorize the parking spot position for future use (DCS bug). + -- TODO: Check for ships and FARPS. + if self.useparkingdb and (takeoff==RAT.wp.cold or takeoff==RAT.wp.hot) and departure:GetCategory()==4 then + self:_AddParkingSpot(departure, group) + end -- Create submenu for this group. if self.f10menu then @@ -1887,7 +1955,10 @@ function RAT:_Respawn(group) -- Note: we have to check that it was supposed to land and not respawned directly after landing or after takeoff. -- TODO: Need to think if continuejourney with respawn_after_takeoff actually makes sense. if landing==RAT.wp.landing and lastpos and not (self.respawn_at_landing or self.respawn_after_takeoff) then - _lastpos=lastpos + -- Check that we have an airport or FARP but not a ship (which would be categroy 1). + if destination:GetCategory()==4 then + _lastpos=lastpos + end end if self.destinationzone then @@ -1933,7 +2004,10 @@ function RAT:_Respawn(group) -- Note: we have to check that it was supposed to land and not respawned directly after landing or after takeoff. -- TODO: Need to think if commute with respawn_after_takeoff actually makes sense. if landing==RAT.wp.landing and lastpos and not (self.respawn_at_landing or self.respawn_after_takeoff) then - _lastpos=lastpos + -- Check that we have landed on an airport or FARP but not a ship (which would be categroy 1). + if destination:GetCategory()==4 then + _lastpos=lastpos + end end -- Handle takeoff type. @@ -1982,7 +2056,7 @@ function RAT:_Respawn(group) end -- Debug - self:F({departure=_departure, destination=_destination, takeoff=_takeoff, landing=_landing, livery=_livery, lastwp=_lastwp}) + self:T2({departure=_departure, destination=_destination, takeoff=_takeoff, landing=_landing, livery=_livery, lastwp=_lastwp}) -- Spawn new group. local arg={} @@ -2009,10 +2083,11 @@ 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 #number takeoff Takeoff type. Could also be air start. +-- @param #number landing 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. +-- @param #table _waypoint Initial waypoint. -- @return Wrapper.Airport#AIRBASE Departure airbase. -- @return Wrapper.Airport#AIRBASE Destination airbase. -- @return #table Table of flight plan waypoints. @@ -2075,8 +2150,8 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- 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) - self:E(RAT.id.."ERROR: "..text) + local text=string.format("ERROR! Specified departure airport %s does not exist for %s.", _departure, self.alias) + self:E(RAT.id..text) end else @@ -2085,9 +2160,8 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- 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() - self:E(RAT.id.."ERROR: "..text) + local text=string.format("ERROR! No valid departure airport could be found for %s.", self.alias) + self:E(RAT.id..text) return nil end @@ -2471,7 +2545,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) text=text..string.format("h_descent_max = %6.1f m\n", h_descent_max) end text=text..string.format("******************************************************\n") - self:T(RAT.id..text) + self:T2(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 @@ -2657,10 +2731,10 @@ function RAT:_PickDeparture(takeoff) if takeoff==RAT.wp.air then dep=ZONE:New(name) else - self:E(RAT.id.."ERROR: Takeoff is not in air. Cannot use "..name.." as departure!") + self:E(RAT.id..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.", name)) end else - self:E(RAT.id.."ERROR: No airport or zone found with name "..name) + self:E(RAT.id..string.format("ERROR: No airport or zone found with name %s.", name)) end -- Add to departures table. @@ -2685,10 +2759,10 @@ function RAT:_PickDeparture(takeoff) else text=string.format("%s: Chosen departure airport: %s (ID %d)", self.alias, departure:GetName(), departure:GetID()) end - MESSAGE:New(text, 30):ToAllIf(self.Debug) + --MESSAGE:New(text, 30):ToAllIf(self.Debug) self:T(RAT.id..text) else - self:E(RAT.id..string.format("ERROR: No departure airport or zone found for %s!", self.alias)) + self:E(RAT.id..string.format("ERROR! No departure airport or zone found for %s.", self.alias)) departure=nil end @@ -2753,10 +2827,10 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) if landing==RAT.wp.air then dest=ZONE:New(name) else - self:E(RAT.id.."ERROR: Landing is not in air. Cannot use zone "..name.." as destination!") + self:E(RAT.id..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!", name)) end else - self:E(RAT.id.."ERROR: No airport or zone found with name "..name) + self:E(RAT.id..string.format("ERROR! No airport or zone found with name %s", name)) end if dest then @@ -2777,7 +2851,7 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) end -- Info message. - self:T(RAT.id.."Number of possible destinations = "..#destinations) + self:T(RAT.id..string.format("Number of possible destinations = %s.", #destinations)) if #destinations > 0 then --- Compare distance of destination airports. @@ -2810,10 +2884,10 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) text=string.format("%s Chosen destination airport: %s (ID %d).", self.alias, destination:GetName(), destination:GetID()) end self:T(RAT.id..text) - MESSAGE:New(text, 30):ToAllIf(self.Debug) + --MESSAGE:New(text, 30):ToAllIf(self.Debug) else - self:E(RAT.id.."ERROR: No destination airport or zone found.") + self:E(RAT.id.."ERROR! No destination airport or zone found.") destination=nil end @@ -2899,7 +2973,7 @@ function RAT:_GetAirportsOfMap() table.insert(self.airports_map, _myab) local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() - self:T2(RAT.id..text) + self:T(RAT.id..text) end end @@ -2923,9 +2997,9 @@ function RAT:_GetAirportsOfCoalition() end if #self.airports==0 then - local text="No possible departure/destination airports found!" - MESSAGE:New(text, 60):ToAll() - self:E(RAT.id.."ERROR: "..text) + local text="ERROR! No possible departure/destination airports found." + MESSAGE:New(text, 30):ToAll() + self:E(RAT.id..text) end end @@ -2937,9 +3011,7 @@ end -- @param #number forID (Optional) Send message only for this ID. function RAT:Status(message, forID) - --message=message or false - --forID=forID or false - + -- Optional arguments. if message==nil then message=false end @@ -2971,6 +3043,8 @@ function RAT:Status(message, forID) local type=self.aircraft.type local status=ratcraft.status local active=ratcraft.active + local Nunits=ratcraft.nunits -- group:GetSize() + local N0units=group:GetInitialSize() -- Monitor time and distance on ground. local Tg=0 @@ -2999,7 +3073,8 @@ function RAT:Status(message, forID) -- If aircraft did not move more than 50 m since last check, we call it stationary and despawn it. -- Aircraft which are spawned uncontrolled or starting their engines are not counted. - if Dg<50 and active and not status==RAT.status.EventBirth then + if Dg<50 and active and status~=RAT.status.EventBirth then + --if Dg<50 and active then stationary=true end @@ -3029,7 +3104,12 @@ function RAT:Status(message, forID) -- Status report. if (forID and spawnindex==forID) or (not forID) then - local text=string.format("ID %i of group %s\n", spawnindex, prefix) + local text=string.format("ID %i of flight %s", spawnindex, prefix) + if N0units>1 then + text=text..string.format(" (%d/%d)\n", Nunits, N0units) + else + text=text.."\n" + end if self.commute then text=text..string.format("%s commuting between %s and %s\n", type, departure, destination) elseif self.continuejourney then @@ -3064,22 +3144,24 @@ function RAT:Status(message, forID) -- 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) + local text=string.format("Group %s is despawned after being %d seconds inaktive on ground.", self.alias, dTlast) self:T(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:T(RAT.id..text) self:_Despawn(group) end end + -- Despawn groups after they have reached their destination zones. if ratcraft.despawnme then local text=string.format("Flight %s will be despawned NOW!", self.alias) self:T(RAT.id..text) -- Despawn old group. - if not self.norespawn then + if (not self.norespawn) and (not self.respawn_after_takeoff) then self:_Respawn(group) end self:_Despawn(group) @@ -3112,10 +3194,10 @@ function RAT:_GetLife(group) if unit then life=unit:GetLife()/unit:GetLife0()*100 else - self:T2(RAT.id.."ERROR: Unit does not exist in RAT_Getlife(). Returning zero.") + self:T2(RAT.id.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.") end else - self:T2(RAT.id.."ERROR: Group does not exist in RAT_Getlife(). Returning zero.") + self:T2(RAT.id.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.") end return life end @@ -3131,10 +3213,6 @@ function RAT:_SetStatus(group, status) -- Get index from groupname. local index=self:GetSpawnIndexFromGroup(group) - if self.Debug or self.reportstatus then - env.info(RAT.id..string.format("Group %s has status %s, spawnindex = %d", group:GetName(), status, index)) - end - if self.ratcraft[index] then -- Set new status. @@ -3161,7 +3239,10 @@ end --- Function is executed when a unit is spawned. -- @param #RAT self +-- @param Core.Event#EVENTDATA EventData function RAT:_OnBirth(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event birth!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP @@ -3178,9 +3259,6 @@ function RAT:_OnBirth(EventData) local text="Event: Group "..SpawnGroup:GetName().." was born." self:T(RAT.id..text) - -- Increase counter of alive groups (also uncontrolled ones). - self.alive=self.alive+1 - -- Set status. local status="unknown in birth" if SpawnGroup:InAir() then @@ -3204,8 +3282,13 @@ function RAT:_OnBirth(EventData) -- Check if aircraft group was accidentally spawned on the runway. -- This can happen due to no parking slots available and other DCS bugs. local onrunway=false - if _takeoff ~= RAT.wp.runway then - onrunway=self:_CheckOnRunway(SpawnGroup, _departure) + if _takeoff ~= RAT.wp.runway and self.checkonrunway then + for _,unit in pairs(SpawnGroup:GetUnits()) do + local _onrunway=self:_CheckOnRunway(unit, _departure) + if _onrunway then + onrunway=true + end + end end -- Workaround if group was spawned on runway. @@ -3214,7 +3297,7 @@ function RAT:_OnBirth(EventData) -- Error message. local text=string.format("ERROR: RAT group of %s was spawned on runway (DCS bug). Group #%d will be despawned immediately!", self.alias, i) MESSAGE:New(text,30):ToAllIf(self.Debug) - env.info(RAT.id..text) + self:T(RAT.id..text) if self.Debug then SpawnGroup:FlareRed() end @@ -3229,19 +3312,18 @@ function RAT:_OnBirth(EventData) -- This creates a completely new group, i.e. livery etc from earlier flights (continuejourney, commute) is not taken over. text=string.format("Try spawning new aircraft of group %s at another location. Attempt %d of max %d.", self.alias,_nrespawn,self.rbug_maxretry) - MESSAGE:New(text,30):ToAllIf(self.Debug) - env.info(RAT.id..text) + MESSAGE:New(text,10):ToAllIf(self.Debug) + self:T(RAT.id..text) -- Spawn new group. self:_SpawnWithRoute(nil, nil, nil, nil, nil, nil, nil, _nrespawn) else - -- This will respawn the same fight (maybe with a different route) but already in the air.l - -- Note: We could also try to spawn already on the runway but this might also lead to problems. - -- Uncontrolled aircraft are not respawned in air. - if not self.uncontrolled then + -- This will respawn the same fight (maybe with a different route) but already in the air. + -- Note: Uncontrolled aircraft are not respawned in air. + if self.respawn_inair and not self.uncontrolled then text=string.format("Spawning new aircraft of group %s in air since no parking slot is available at %s.", self.alias, _departure) - MESSAGE:New(text,30):ToAll() - env.info(RAT.id..text) + MESSAGE:New(text,10):ToAll() + self:T(RAT.id..text) -- Spawn new group at this airport but already in air. self:_SpawnWithRoute(_departure, _destination, RAT.wp.air, _landing, _livery) @@ -3249,6 +3331,23 @@ function RAT:_OnBirth(EventData) end end -- end of workaround + -- Check if any unit of the group was spawned on top of another unit in the MOOSE data base. + local ontop=false + if self.checkontop then + ontop=self:_CheckOnTop(SpawnGroup) + end + + if ontop then + local text=string.format("ERROR: RAT group of %s was spawned on top of another unit. Group #%d will be despawned immediately!", self.alias, i) + MESSAGE:New(text,30):ToAllIf(self.Debug) + self:T(RAT.id..text) + if self.Debug then + SpawnGroup:FlareYellow() + end + -- Despawn group. + self:_Despawn(SpawnGroup) + end + end end else @@ -3256,73 +3355,13 @@ function RAT:_OnBirth(EventData) end end ---- Function to check whether an aircraft is on the runway. --- @param #RAT self --- @param Wrapper.Group#GROUP group The group to be checked. --- @param #string airport The name of the airport we want to check. --- @param #boolean True if aircraft is on the runway and on the ground. -function RAT:_CheckOnRunway(group,airport) - - -- We use the tabulated points in the ATC_GROUND classes to find out if the group is on the runway. - -- Note that land.SurfaceType.RUNWAY also is true for the parking areas etc. Hence, not useful. - -- This is useful to check if an aircraft was accidentally spawned on the runway due to missing parking spots. - - --BASE:E(ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways) - - -- Table holding the points around the runway. - local pointsrwy={} - - -- Loop over all airports on Caucaus map. - for id,name in pairs(AIRBASE.Caucasus) do - if name==airport then - --pointsrwy=ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways - pointsrwy=ATC_GROUND_CAUCASUS.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - -- Loop over all airports on NTTR map. - for id,name in pairs(AIRBASE.Nevada) do - if name==airport then - pointsrwy=ATC_GROUND_NEVADA.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - -- Loop over all airports on Normandy map. - for id,name in pairs(AIRBASE.Normandy) do - if name==airport then - pointsrwy=ATC_GROUND_NORMANDY.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - - -- Assume we are not on the runway. - local onrunway=false - - -- Loop over all runways. Some airports have more than one. - for PointsRunwayID, PointsRunway in pairs(pointsrwy) do - -- Create zone around runway. - local runway = ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID, PointsRunway) - - -- Check if group is completely or partly inside the zone. - -- Note that IsPartlyInZone is only true if units are inside AND outside of the zone. - if group:IsCompletelyInZone(runway) or group:IsPartlyInZone(runway) then - onrunway=true - end - end - - -- Check that aircraft is on ground. - onrunway=onrunway and group:InAir()==false - - if self.Debug then - env.info(RAT.id..string.format("Check on runway of %s airport for group %s = %s", airport, group:GetName(),tostring(onrunway))) - end - - return onrunway -end --- Function is executed when a unit starts its engines. -- @param #RAT self -function RAT:_EngineStartup(EventData) +-- @param Core.Event#EVENTDATA EventData +function RAT:_OnEngineStartup(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event EngineStartup!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP @@ -3357,6 +3396,7 @@ end --- Function is executed when a unit takes off. -- @param #RAT self +-- @param Core.Event#EVENTDATA EventData function RAT:_OnTakeoff(EventData) local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP @@ -3382,8 +3422,9 @@ function RAT:_OnTakeoff(EventData) text="Event: Group "..SpawnGroup:GetName().." will be respawned." self:T(RAT.id..text) - -- Respawn group. - self:_Respawn(SpawnGroup) + -- Respawn group. We respawn with no parameters from the old flight. + self:_SpawnWithRoute(nil, nil, nil, nil, nil, nil, nil, nil) + --self:_Respawn(SpawnGroup) end end @@ -3396,6 +3437,7 @@ end --- Function is executed when a unit lands. -- @param #RAT self +-- @param Core.Event#EVENTDATA EventData function RAT:_OnLand(EventData) local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP @@ -3441,6 +3483,8 @@ end --- Function is executed when a unit shuts down its engines. -- @param #RAT self function RAT:_OnEngineShutdown(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event EngineShutdown!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP @@ -3454,27 +3498,30 @@ function RAT:_OnEngineShutdown(EventData) -- 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." - self:T(RAT.id..text) - - -- Set status. - local status=RAT.status.EventEngineShutdown - self:_SetStatus(SpawnGroup, status) + -- Despawn group only if it on the ground. + if not SpawnGroup:InAir() then - if not self.respawn_at_landing and not self.norespawn then - text="Event: Group "..SpawnGroup:GetName().." will be respawned." + local text="Event: Group "..SpawnGroup:GetName().." shut down its engines." self:T(RAT.id..text) - - -- Respawn group. - self:_Respawn(SpawnGroup) - end - - - -- Despawn group. - text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." - self:T(RAT.id..text) - self:_Despawn(SpawnGroup) + + -- Set status. + 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." + self:T(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + + -- Despawn group. + text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." + self:T(RAT.id..text) + self:_Despawn(SpawnGroup) + end end end @@ -3483,15 +3530,86 @@ function RAT:_OnEngineShutdown(EventData) end end ---- Function is executed when a unit is dead. +--- Function is executed when a unit is hit. -- @param #RAT self -function RAT:_OnDead(EventData) - +-- @param Core.Event#EVENTDATA EventData +function RAT:_OnHit(EventData) + self:F3(EventData) + self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s", self.alias, tostring(EventData.IniUnitName), tostring(EventData.TgtUnitName))) + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP if SpawnGroup then - env.info(string.format("%sGroup %s died!", 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 + + -- Debug info. + self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.", SpawnGroup:GetName(), EventData.IniUnitName)) + + end + end + end +end + +--- Function is executed when a unit is dead or crashes. +-- @param #RAT self +-- @param Core.Event#EVENTDATA EventData +function RAT:_OnDeadOrCrash(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event DeadOrCrash!") + + 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 + + -- Decrease group alive counter. + self.alive=self.alive-1 + + -- Debug info. + local text=string.format("Event: Group %s crashed or died. Alive counter = %d.", SpawnGroup:GetName(), self.alive) + self:T(RAT.id..text) + + -- Split crash and dead events. + if EventData.id == world.event.S_EVENT_CRASH then + + -- Call crash event. This handles when a group crashed or + self:_OnCrash(EventData) + + elseif EventData.id == world.event.S_EVENT_DEAD then + + -- Call dead event. + self:_OnDead(EventData) + + end + end + end + end +end + +--- Function is executed when a unit is dead. +-- @param #RAT self +-- @param Core.Event#EVENTDATA EventData +function RAT:_OnDead(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event Dead!") + + 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) @@ -3501,9 +3619,8 @@ function RAT:_OnDead(EventData) -- Check that the template name actually belongs to this object. if EventPrefix == self.alias then - local text="Event: Group "..SpawnGroup:GetName().." died." + local text=string.format("Event: Group %s died. Unit %s.", SpawnGroup:GetName(), EventData.IniUnitName) self:T(RAT.id..text) - env.info(RAT.id..text) -- Set status. local status=RAT.status.EventDead @@ -3513,20 +3630,20 @@ function RAT:_OnDead(EventData) end else - self:E(RAT.id.."ERROR: Group does not exist in RAT:_OnDead().") + self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnDead().") end end --- Function is executed when a unit crashes. -- @param #RAT self +-- @param Core.Event#EVENTDATA EventData function RAT:_OnCrash(EventData) + self:F3(EventData) + self:T3(RAT.id.."Captured event Crash!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP if SpawnGroup then - - self:T(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) - 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) @@ -3535,18 +3652,29 @@ function RAT:_OnCrash(EventData) -- Check that the template name actually belongs to this object. if EventPrefix == self.alias then - - local text="Event: Group "..SpawnGroup:GetName().." crashed." + + -- Update number of alive units in the group. + local _i=self:GetSpawnIndexFromGroup(SpawnGroup) + self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 + local _n=self.ratcraft[_i].nunits + local _n0=SpawnGroup:GetInitialSize() + + -- Debug info. + local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.", SpawnGroup:GetName(), EventData.IniUnitName, _n, _n0) self:T(RAT.id..text) - 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? - + -- Respawn group if all units are dead. + if _n==0 and self.respawn_after_crash and not self.norespawn then + local text=string.format("No units left of group %s. Group will be respawned now.", SpawnGroup:GetName()) + self:T(RAT.id..text) + -- Respawn group. + self:_Respawn(SpawnGroup) + end + --TODO: Maybe spawn some people at the crash site and send a distress call. -- And define them as cargo which can be rescued. end @@ -3575,6 +3703,8 @@ function RAT:_Despawn(group) self.ratcraft[index].group=nil self.ratcraft[index]["status"]="Dead" + --TODO: Maybe here could be some more arrays deleted? + --TODO: Somehow this causes issues. --[[ --self.ratcraft[index]["group"]=group self.ratcraft[index]["destination"]=nil @@ -3597,26 +3727,77 @@ function RAT:_Despawn(group) self.ratcraft[index].despawnme=nil self.ratcraft[index].nrespawn=nil ]] - -- Remove ratcraft table entry. - --TODO: Somehow this causes issues. --table.remove(self.ratcraft, index) - --TODO: What events are actually fired when doing this? Crash and Dead or just Dead or...? - -- Destroy should create a crash event but for each unit. - group:Destroy() - - -- Decrease group alive counter. - self.alive=self.alive-1 + + -- This will destroy the DCS group and create a single DEAD event. + self:_Destroy(group) -- Remove submenu for this group. if self.f10menu and self.SubMenuName ~= nil then self.Menu[self.SubMenuName]["groups"][index]:Remove() end + end end +end - --TODO: Maybe here could be some more arrays deleted? +--- Destroys the RAT DCS group and all of its DCS units. +-- Note that this raises a DEAD event at run-time. +-- So all event listeners will catch the DEAD event of this DCS group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group The RAT group to be destroyed. +function RAT:_Destroy(group) + self:F2(group) + + local DCSGroup = group:GetDCSObject() -- Dcs.DCSGroup#Group + + if DCSGroup and DCSGroup:isExist() then + + --local DCSUnit = DCSGroup:getUnit(1) -- Dcs.DCSUnit#Unit + --if DCSUnit then + -- self:_CreateEventDead(timer.getTime(), DCSUnit) + --end + + -- Cread one single Dead event and delete units from database. + local triggerdead=true + for _,DCSUnit in pairs(DCSGroup:getUnits()) do + + -- Dead event. + if DCSUnit then + if triggerdead then + self:_CreateEventDead(timer.getTime(), DCSUnit) + triggerdead=false + end + + -- Delete from data base. + _DATABASE:DeleteUnit(DCSUnit:getName()) + end + end + + -- Destroy DCS group. + DCSGroup:destroy() + DCSGroup = nil + end + + return nil +end + +--- Create a Dead event. +-- @param #RAT 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 RAT:_CreateEventDead(EventTime, Initiator) + self:F( { EventTime, Initiator } ) + + local Event = { + id = world.event.S_EVENT_DEAD, + time = EventTime, + initiator = Initiator, + } + + world.onEvent( Event ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3723,9 +3904,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport text=text..string.format("No airport/zone specified\n") end text=text.."******************************************************\n" - if self.Debug then - self:T2(RAT.id..text) - end + self:T2(RAT.id..text) -- define waypoint local RoutePoint = {} @@ -3829,10 +4008,8 @@ function RAT:_Routeinfo(waypoints, comment) 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 + -- Debug info. + self:T2(RAT.id..text) -- return total route length in meters return total @@ -3919,8 +4096,6 @@ function RAT._WaypointFunction(group, rat, wp) -- New status. local status=rat.waypointstatus[wp] - - --rat.ratcraft[sdx].status=status rat:_SetStatus(group, status) if wp==WPholding then @@ -3945,7 +4120,7 @@ function RAT._WaypointFunction(group, rat, wp) 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) + MESSAGE:New(text, 10):ToAllIf(rat.Debug) BASE.T(rat, RAT.id..text) -- Enable despawn switch. Next time the status function is called, the aircraft will be despawned. rat.ratcraft[sdx].despawnme=true @@ -3995,36 +4170,359 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Calculate the max flight level for a given distance and fixed climb and descent rates. This function is obsolete now. --- 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. +--- Randomly activates an uncontrolled aircraft. -- @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 - self:T3(RAT.id..text) +function RAT:_ActivateUncontrolled() + self:F() + + -- Spawn indices of uncontrolled inactive aircraft. + local idx={} + local rat={} + + -- Number of active aircraft. + local nactive=0 + + -- Loop over RAT groups and count the active ones. + for spawnindex,ratcraft in pairs(self.ratcraft) do + + local group=ratcraft.group --Wrapper.Group#GROUP + + if group and group:IsAlive() then + + local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s.", ratcraft.group:GetName(), spawnindex, tostring(ratcraft.active)) + self:T2(RAT.id..text) + + if ratcraft.active then + nactive=nactive+1 + else + table.insert(idx, spawnindex) + end + + end end - return h3+h0 + + -- Debug message. + local text=string.format("Uncontrolled: Ninactive = %d, Nactive = %d (of max %d).", #idx, nactive, self.activate_max) + self:T(RAT.id..text) + + if #idx>0 and nactive self.aircraft.box*2 + -- Or (if possible) even better to take our and the other object's size (plus 10% safety margin) + local size=self:_GetObjectSize(unit) + if size then + safe=_dist > (self.aircraft.box+size)*1.1 + end + self:T2(RAT.id..string.format("RAT aircraft size = %.1f m, other object size = %.1f m", self.aircraft.box, size or 0)) + if not safe then + occupied=true + end + self:T2(RAT.id..string.format("Unit %s to parking spot %d: distance = %.1f m (occupied = %s).", unit:GetName(), _i, _dist, tostring(safe))) + end + end + end + + if occupied then + self:T(RAT.id..string.format("Parking spot #%d occupied at %s.", _i, airport)) + else + parkingspot=spawnplace + self:T(RAT.id..string.format("Found free parking spot in DB at airport %s.", airport)) + break + end + + end + + return parkingspot + else + self:T2(RAT.id..string.format("No parking position in DB yet for %s.", airport)) + end + + self:T(RAT.id..string.format("No free parking position found in DB at airport %s.", airport)) + return nil +end + +--- Get aircraft dimensions length, width, height. +-- @param #RAT self +-- @param Wrapper.Unit#UNIT unit The unit which is we want the size of. +-- @return #number Size, i.e. max(length,width) of unit. +function RAT:_GetObjectSize(unit) + local DCSunit=unit:GetDCSObject() + if DCSunit then + local DCSdesc=DCSunit:getDesc() + -- dimensions + local length=DCSdesc.box.max.x + local height=DCSdesc.box.max.y + local width=DCSdesc.box.max.z + return math.max(length,width) + end + return nil +end + +--- Find aircraft that have accidentally been spawned on top of each other. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Units of this group will be checked. +-- @return #boolean True if group was destroyed because it was on top of another unit. False if otherwise. +function RAT:_CheckOnTop(group) + + -- Minimum allowed distance between two units + local distmin=5 + + for i,uniti in pairs(group:GetUnits()) do + local uniti=uniti --Wrapper.Unit#UNIT + + if uniti then + + local namei=uniti:GetName() + + for j,unitj in pairs(_DATABASE.UNITS) do + + if unitj then + local unitj=unitj --Wrapper.Unit#UNIT + local namej=unitj:GetName() + + if namei ~= namej then + + local DCSuniti=uniti:GetDCSObject() + local DCSunitj=unitj:GetDCSObject() + + if DCSuniti and DCSuniti:isExist() and DCSunitj and DCSunitj:isExist() then + + -- Distance between units. + local _dist=uniti:GetCoordinate():Get2DDistance(unitj:GetCoordinate()) + + -- Check for min distance. + if _dist < distmin then + if not uniti:InAir() and not unitj:InAir() then + --uniti:Destroy() + --self:_CreateEventDead(timer.getTime(), uniti) + --unitj:Destroy() + --self:_CreateEventDead(timer.getTime(), unitj) + return true + end + end + + end -- if DCSunit exists + end -- if namei==namej then + end --if unitj then + end -- for j, unitj + end -- if uniti then + end -- for i,uniti in + + return false +end + +--- Function to check whether an aircraft is on the runway. +-- @param #RAT self +-- @param Wrapper.Unit#UNIT unit The unit to be checked. +-- @param #string airport The name of the airport we want to check. +-- @return #boolean True if aircraft is on the runway and on the ground. +function RAT:_CheckOnRunway(unit, airport) + + -- We use the tabulated points in the ATC_GROUND classes to find out if the group is on the runway. + -- Note that land.SurfaceType.RUNWAY also is true for the parking areas etc. Hence, not useful. + -- This is useful to check if an aircraft was accidentally spawned on the runway due to missing parking spots. + --BASE:E(ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways) + + -- Table holding the points around the runway. + local pointsrwy={} + + -- Loop over all airports on Caucaus map. + for id,name in pairs(AIRBASE.Caucasus) do + if name==airport then + --pointsrwy=ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways + pointsrwy=ATC_GROUND_CAUCASUS.Airbases[name].PointsRunways + self:T2({name=name, points=pointsrwy}) + end + end + -- Loop over all airports on NTTR map. + for id,name in pairs(AIRBASE.Nevada) do + if name==airport then + pointsrwy=ATC_GROUND_NEVADA.Airbases[name].PointsRunways + self:T2({name=name, points=pointsrwy}) + end + end + -- Loop over all airports on Normandy map. + for id,name in pairs(AIRBASE.Normandy) do + if name==airport then + pointsrwy=ATC_GROUND_NORMANDY.Airbases[name].PointsRunways + self:T2({name=name, points=pointsrwy}) + end + end + + -- Assume we are not on the runway. + local onrunway=false + + -- Loop over all runways. Some airports have more than one. + for PointsRunwayID, PointsRunway in pairs(pointsrwy) do + -- Create zone around runway. + local runway = ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID, PointsRunway) + + -- Check if unit is in on the runway. + if runway:IsVec3InZone(unit:GetVec3()) then + onrunway=true + end + end + + -- Check that aircraft is on ground. + onrunway=onrunway and unit:InAir()==false + + -- Debug + self:T(RAT.id..string.format("Check on runway of %s airport for unit %s = %s", airport, unit:GetName(),tostring(onrunway))) + + return onrunway +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]. @@ -4298,6 +4796,8 @@ 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". @@ -4306,6 +4806,7 @@ end -- @param #string livery (Optional) Livery of the aircraft. All members of a flight will get the same livery. -- @param Core.Point#COORDINATE spawnplace (Optional) Place where spawning should happen. If not present, first waypoint is taken. function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) + self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace}) -- 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} @@ -4346,6 +4847,11 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y + + if self.Debug then + local unitspawn=COORDINATE:New(TX,PointVec3.y,TY) + unitspawn:MarkToAll(string.format("Spawnplace unit #%d", UnitID)) + end SpawnTemplate.units[UnitID].heading = heading SpawnTemplate.units[UnitID].psi = -heading @@ -4382,7 +4888,6 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) 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" :) @@ -4732,26 +5237,26 @@ end -- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft -- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours. -- --- local a10c=RAT:New("RAT_A10C", "A-10C managed") --- a10c:SetDeparture({"Batumi"}) --- --- local f15c=RAT:New("RAT_F15C", "F15C managed") --- f15c:SetDeparture({"Sochi-Adler"}) --- f15c:DestinationZone() --- f15c:SetDestination({"Zone C"}) --- --- local av8b=RAT:New("RAT_AV8B", "AV8B managed") --- av8b:SetDeparture({"Zone C"}) --- av8b:SetTakeoff("air") --- av8b:DestinationZone() --- av8b:SetDestination({"Zone A"}) --- --- local manager=RATMANAGER:New(25) --- manager:Add(a10c, 5) --- manager:Add(f15c, 5) --- manager:Add(av8b, 5) --- manager:Start(30) --- manager:Stop(7200) +-- local a10c=RAT:New("RAT_A10C", "A-10C managed") +-- a10c:SetDeparture({"Batumi"}) +-- +-- local f15c=RAT:New("RAT_F15C", "F15C managed") +-- f15c:SetDeparture({"Sochi-Adler"}) +-- f15c:DestinationZone() +-- f15c:SetDestination({"Zone C"}) +-- +-- local av8b=RAT:New("RAT_AV8B", "AV8B managed") +-- av8b:SetDeparture({"Zone C"}) +-- av8b:SetTakeoff("air") +-- av8b:DestinationZone() +-- av8b:SetDestination({"Zone A"}) +-- +-- local manager=RATMANAGER:New(25) +-- manager:Add(a10c, 5) +-- manager:Add(f15c, 5) +-- manager:Add(av8b, 5) +-- manager:Start(30) +-- manager:Stop(7200) -- -- @field #RATMANAGER RATMANAGER={ @@ -5029,7 +5534,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive) end -- Debug info - --env.info(string.format("RATMANAGER: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d", j, alive[j], min[j], mini[j], maxi[j], N[j],sN, sP)) + self:T3(string.format("RATMANAGER: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d", j, alive[j], min[j], mini[j], maxi[j], N[j],sN, sP)) end