diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index b9b3b139f..5699892f7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5176,7 +5176,7 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local assetitem=_asset --#WAREHOUSE.Assetitem -- Debug info. - self:T2(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) + self:I(self.lid..string.format("Asset %s spawned=%s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) if assetitem.spawned then n=n+1 @@ -5680,7 +5680,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol local AirbaseCategory = self:GetAirbaseCategory() -- Check enough parking spots. - if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then + if AirbaseCategory==Airbase.Category.HELIPAD then + + elseif AirbaseCategory==Airbase.Category.SHIP then --TODO Figure out what's necessary in this case. @@ -7547,17 +7549,29 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) for i,unit in pairs(units) do local coord=COORDINATE:New(unit.x, unit.alt, unit.y) coords[unit.name]=coord - --[[ - local airbase=coord:GetClosestAirbase() - local _,TermID, dist, spot=coord:GetClosestParkingSpot(airbase) - if dist<=10 then - env.info(string.format("Found client %s on parking spot %d at airbase %s", unit.name, TermID, airbase:GetName())) - end - ]] end end return coords end + + if airbase:GetAirbaseCategory()==Airbase.Category.SHIP then + -- Scan a radius of 100 meters around the spot. + local _,_,_,units=airbase:GetCoordinate():ScanObjects(200, true, false, false) + local N=0 + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + if not unit:InAir() then + N=N+1 + end + end + env.info(string.format("FF number of units on deck=%d", N)) + if #assets>=6 and N==0 then + -- More than 6 is difficult. We need an empty deck. + elseif N+#assets>6 then + -- wait until max 6 ac are on deck. + return {} + end + end -- Get parking spot data table. This contains all free and "non-free" spots. local parkingdata=airbase:GetParkingSpotsTable() diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f3eabf346..e81f2439d 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -720,7 +720,7 @@ function AIRWING:onafterStatus(From, Event, To) local skill=squadron.skill and tostring(squadron.skill) or "N/A" -- Squadron text - text=text..string.format("\n* %s %s: %s*%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, #squadron.assets, callsign, modex, skill) + text=text..string.format("\n* %s %s: %s*%d (avail %d), Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, #squadron.assets, squadron:CountAssetsInStock(), callsign, modex, skill) -- Loop over all assets. if self.verbose>0 then @@ -773,7 +773,10 @@ function AIRWING:onafterStatus(From, Event, To) -------------- -- Mission --- -------------- - + + -- Check if any missions should be cancelled. + self:_CheckMissions() + -- Get next mission. local mission=self:_GetNextMission() @@ -975,6 +978,24 @@ function AIRWING:GetTankerForFlight(flightgroup) end +--- Get next mission. +-- @param #AIRWING self +function AIRWING:_CheckMissions() + + -- Loop over missions in queue. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission:IsNotOver() then + + if mission:IsReadyToCancel() then + mission:Cancel() + end + + end + end + +end --- Get next mission. -- @param #AIRWING self -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 12c850d90..acf14ec3a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -37,7 +37,9 @@ -- @field #number Ntargets Number of mission targets. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. --- @field #table startconditions Conditions that have to be true, before the mission is started. +-- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. +-- @field #table conditionSuccess If all stop conditions are true, the mission is cancelled. +-- @field #table conditionFailure If all stop conditions are true, the mission is cancelled. -- -- @field #number orbitSpeed Orbit speed in m/s. -- @field #number orbitAltitude Orbit altitude in meters. @@ -164,7 +166,9 @@ AUFTRAG = { missionFraction = 0.5, enrouteTasks = {}, marker = nil, - startconditions = {}, + conditionStart = {}, + conditionSuccess = {}, + conditionFailure = {}, } --- Global mission counter. @@ -299,10 +303,10 @@ AUFTRAG.TargetType={ -- @field #string DAMAGED Target was damaged. -- @field #string DESTROYED Target was destroyed. ---- Mission start condition. --- @type AUFTRAG.ConditionStart --- @field #function funcReady Callback function to check whether the mission can be started. Should return a #boolean. --- @field #table argReady Arguments passed to the callback function. +--- Generic mission condition. +-- @type AUFTRAG.Condition +-- @field #function func Callback function to check for a condition. Should return a #boolean. +-- @field #table arg Optional arguments passed to the condition callback function. --- Flight specific data. Each flight subscribed to this mission has different data for this. -- @type AUFTRAG.FlightData @@ -316,7 +320,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.1.7" +AUFTRAG.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1235,17 +1239,57 @@ end -- @param #function ConditionFunction Function that needs to be true before the mission can be started. Must return a #boolean. -- @param ... Condition function arguments if any. -- @return #AUFTRAG self -function AUFTRAG:AddStartCondition(ConditionFunction, ...) +function AUFTRAG:AddConditionStart(ConditionFunction, ...) - local condition={} --#AUFTRAG.ConditionStart + local condition={} --#AUFTRAG.Condition - condition.funcReady=ConditionFunction - condition.argReady={} + condition.func=ConditionFunction + condition.arg={} if arg then - condition.argReady=arg + condition.arg=arg end - table.insert(self.startconditions, condition) + table.insert(self.conditionStart, condition) + + return self +end + +--- Add success condition. +-- @param #AUFTRAG self +-- @param #function ConditionFunction If this function returns `true`, the mission is cancelled. +-- @param ... Condition function arguments if any. +-- @return #AUFTRAG self +function AUFTRAG:AddConditionSuccess(ConditionFunction, ...) + + local condition={} --#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionSuccess, condition) + + return self +end + +--- Add failure condition. +-- @param #AUFTRAG self +-- @param #function ConditionFunction If this function returns `true`, the mission is cancelled. +-- @param ... Condition function arguments if any. +-- @return #AUFTRAG self +function AUFTRAG:AddConditionFailure(ConditionFunction, ...) + + local condition={} --#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionFailure, condition) return self end @@ -1328,6 +1372,13 @@ function AUFTRAG:IsScheduled() return self.status==AUFTRAG.Status.SCHEDULED end +--- Check if mission is STARTED, i.e. flight is on its way to the mission execution waypoint. +-- @param #AUFTRAG self +-- @return #boolean If true, mission is started. +function AUFTRAG:IsStarted() + return self.status==AUFTRAG.Status.STARTED +end + --- Check if mission is executing. -- @param #AUFTRAG self -- @return #boolean If true, mission is currently executing. @@ -1374,7 +1425,7 @@ end --- Check if mission is ready to be started. -- * Mission start time passed. -- * Mission stop time did not pass already. --- * Any start conditions are true. +-- * All start conditions are true. -- @param #AUFTRAG self -- @return #boolean If true, mission can be started. function AUFTRAG:IsReadyToGo() @@ -1391,23 +1442,100 @@ function AUFTRAG:IsReadyToGo() return false end - -- All start conditions must be true. - for _,_condition in pairs(self.startconditions or {}) do - local condition=_condition --#AUFTRAG.ConditionStart + -- All start conditions true? + local startme=self:EvalConditionsAll(self.conditionStart) + + if not startme then + return false + end + + + -- We're good to go! + return true +end + +--- Check if mission is ready to be started. +-- * Mission stop already passed. +-- * Any stop condition is true. +-- @param #AUFTRAG self +-- @return #boolean If true, mission should be cancelled. +function AUFTRAG:IsReadyToCancel() + + local Tnow=timer.getAbsTime() + + -- Stop time already passed. + if self.Tstop and Tnow>self.Tstop then + return true + end + + + local failure=self:EvalConditionsAny(self.conditionFailure) + + if failure then + self.failurecondition=true + return true + end + + local success=self:EvalConditionsAny(self.conditionSuccess) + + if success then + self.successcondition=true + return true + end + + -- No criterion matched. + return false +end + +--- Check if all given condition are true. +-- @param #AUFTRAG self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false. +function AUFTRAG:EvalConditionsAll(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --#AUFTRAG.Condition -- Call function. - local ready=condition.funcReady(unpack(condition.argReady)) + local istrue=condition.func(unpack(condition.arg)) - if not ready then + -- Any false will return false. + if not istrue then return false end end - -- We're good to go! + -- All conditions were true. return true end + +--- Check if any of the given conditions is true. +-- @param #AUFTRAG self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, at least one condition is true. +function AUFTRAG:EvalConditionsAny(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --#AUFTRAG.Condition + + -- Call function. + local istrue=condition.func(unpack(condition.arg)) + + -- Any true will return true. + if istrue then + return true + end + + end + + -- No condition was true. + return false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mission Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1500,6 +1628,15 @@ function AUFTRAG:Evaluate() -- Assume success and check if any failed condition applies. local failed=false + + -- Any success condition true? + local successCondition=self:EvalConditionsAny(self.conditionSuccess) + + -- Any failure condition true? + local failureCondition=self:EvalConditionsAny(self.conditionFailure) + + -- Target damage in %. + local targetdamage=self:GetTargetDamage() -- Current number of mission targets. local Ntargets=self:CountMissionTargets() @@ -1511,7 +1648,21 @@ function AUFTRAG:Evaluate() --TODO: all assets dead? Is this a FAILED criterion even if all targets have been destroyed? What if there are no initial targets (e.g. when ORBIT, PATROL, RECON missions). - self:I(self.lid..string.format("Evaluating mission: Initial Targets=%d, current targets=%d ==> success=%s", self.Ntargets, Ntargets, tostring(not failed))) + --self:I(self.lid..string.format("Evaluating mission: Initial Targets=%d, current targets=%d ==> success=%s", self.Ntargets, Ntargets, tostring(not failed))) + + if failureCondition then + failed=true + elseif successCondition then + failed=false + end + + local text=string.format("Evaluating mission:\n") + text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) + text=text..string.format("Damage = %.1f %%\n", targetdamage) + text=text..string.format("Success Cond = %s\n", tostring(successCondition)) + text=text..string.format("Failure Cond = %s", tostring(failureCondition)) + self:I(self.lid..text) + if failed then self:Failed() @@ -1652,6 +1803,10 @@ function AUFTRAG:CheckFlightsDone() return false end + -- It could be that all flights were destroyed on the way to the mission execution waypoint. + if self:IsStarted() and self:CountFlightGroups()==0 then + return true + end -- Check status of all flight groups. for groupname,data in pairs(self.flightdata) do @@ -2157,24 +2312,45 @@ end --- Get target damage. -- @param #AUFTRAG self --- @return #number Percent. +-- @return #number Damage in percent. function AUFTRAG:GetTargetDamage() local target=self:GetTargetData() - local life=self:GetTargetLife()/self:GetTargetData().Lifepoints + local life=self:GetTargetLife()/self:GetTargetInitialLife() local damage=1-life return damage*100 end + +--- Get target life points. +-- @param #AUFTRAG self +-- @return #number Life points of target. +function AUFTRAG:GetTargetLife() + return self:_GetTargetLife(nil, false) +end + --- Get target life points. -- @param #AUFTRAG self -- @param #AUFTRAG.TargetData Target (Optional) The target object. +-- @param #boolean Healthy Get the life points of the healthy target. -- @return #number Life points of target. -function AUFTRAG:GetTargetLife(Target) +function AUFTRAG:_GetTargetLife(Target, Healthy) local N=0 Target=Target or self:GetTargetData() + local function _GetLife(unit) + local unit=unit --Wrapper.Unit#UNIT + if Healthy then + local life=unit:GetLife() + local life0=unit:GetLife0() + + return math.max(life, life0) + else + return unit:GetLife() + end + end + if Target then if Target.Type==AUFTRAG.TargetType.GROUP then @@ -2188,7 +2364,7 @@ function AUFTRAG:GetTargetLife(Target) -- We check that unit is "alive". if unit and unit:IsAlive() then - N=N+unit:GetLife() + N=N+_GetLife(unit) end end @@ -2197,15 +2373,18 @@ function AUFTRAG:GetTargetLife(Target) local target=Target.Target --Wrapper.Unit#UNIT if target and target:IsAlive() then - N=N+target:GetLife() + N=N+_GetLife(target) end elseif Target.Type==AUFTRAG.TargetType.STATIC then local target=Target.Target --Wrapper.Static#STATIC + -- Statics are alive or not. if target and target:IsAlive() then - N=N+1 --target:GetLife() + N=N+1 --_GetLife(target) + else + N=N+0 end elseif Target.Type==AUFTRAG.TargetType.AIRBASE then @@ -2230,7 +2409,7 @@ function AUFTRAG:GetTargetLife(Target) -- We check that unit is "alive". if unit and unit:IsAlive() then - N=N+unit:GetLife() + N=N+_GetLife(unit) end end @@ -2243,13 +2422,13 @@ function AUFTRAG:GetTargetLife(Target) -- We check that unit is "alive". if unit and unit:IsAlive() then - N=N+unit:GetLife() + N=N+_GetLife(unit) end end else - self:E("ERROR unknown target type") + self:E(self.lid.."ERROR unknown target type") end end @@ -2788,7 +2967,7 @@ function AUFTRAG:_TargetFromObject(Object) local Ninitial=self:CountMissionTargets(target) -- Initial total life point. - local Lifepoints=self:GetTargetLife(target) + local Lifepoints=self:_GetTargetLife(target, true) -- Set engage Target. self.engageTarget=target @@ -2798,8 +2977,8 @@ function AUFTRAG:_TargetFromObject(Object) -- TODO: get rid of this. self.Ntargets=Ninitial - env.info("FF check") - self:I(self.lid..string.format("Mission Target Type=%s, Ntargets=%d", target.Type, self.Ntargets)) + -- Debug info. + self:I(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", target.Name, target.Type, Ninitial, Lifepoints)) return target end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 42457c003..0d52d3a6c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2416,8 +2416,9 @@ end function FLIGHTGROUP:onafterFlightArrived(From, Event, To) self:T(self.lid..string.format("Flight arrived")) - -- Remove flight from landing queue. + -- Flight Control if self.flightcontrol then + -- Remove flight from landing queue. self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qtaxiinb, self, "TAXI_INB") -- Add flight to arrived queue. self.flightcontrol:_AddFlightToArrivedQueue(self) @@ -2829,7 +2830,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:T(self.lid..text) - local althold=self.ishelo and 1000 or 6000 + local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -- Holding points. local c0=self.group:GetCoordinate() @@ -2874,7 +2875,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp self.flaghold:Set(0) -- Task fuction when reached holding point. - local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self) + local TaskArrived=self.group:TaskFunction("FLIGHTGROUP.", self) -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. local TaskOrbit=self.group:TaskOrbit(p0, nil, UTILS.KnotsToMps(SpeedHold), p1) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 6cfecf3c8..f10a031d0 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -614,6 +614,24 @@ function SQUADRON:CanMission(Mission) return true end +--- Get assets for a mission. +-- @param #SQUADRON self +-- @return #number Assets not spawned. +function SQUADRON:CountAssetsInStock() + + local N=0 + for _,_asset in pairs(self.assets) do + local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset + if asset.spawned then + + else + N=N+1 + end + end + + return N +end + --- Get assets for a mission. -- @param #SQUADRON self -- @param Ops.Auftrag#AUFTRAG Mission The mission.