diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 95d211370..8a941f0db 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -83,6 +83,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 210e5d856..9f8f4ba9d 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -582,7 +582,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Recovery Windows --- - if self.verbose>=2 then + if self.verbose>=2 and #self.Qintowind>0 then -- Debug output: local text=string.format(self.lid.."Turn into wind time windows:") diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8c10af305..d49774f96 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -375,7 +375,6 @@ OPSGROUP.TaskType={ --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus -- @field #string NOTCARRIER This group is not a carrier yet. --- @field #string EMPTY Carrier is empty and ready for cargo transport. -- @field #string PICKUP Carrier is on its way to pickup cargo. -- @field #string LOADING Carrier is loading cargo. -- @field #string LOADED Carrier has loaded cargo. @@ -383,7 +382,6 @@ OPSGROUP.TaskType={ -- @field #string UNLOADING Carrier is unloading cargo. OPSGROUP.CarrierStatus={ NOTCARRIER="not carrier", - EMPTY="empty", PICKUP="pickup", LOADING="loading", LOADED="loaded", @@ -394,18 +392,14 @@ OPSGROUP.CarrierStatus={ --- Cargo status. -- @type OPSGROUP.CargoStatus -- @field #string NOTCARGO This group is no cargo yet. --- @field #string WAITING Cargo is awaiting transporter. -- @field #string ASSIGNED Cargo is assigned to a carrier. -- @field #string BOARDING Cargo is boarding a carrier. -- @field #string LOADED Cargo is loaded into a carrier. --- @field #string DELIVERED Cargo was delivered at its destination. OPSGROUP.CargoStatus={ NOTCARGO="not cargo", - WAITING="waiting for carrier", ASSIGNED="assigned to carrier", BOARDING="boarding", LOADED="loaded", - DELIVERED="delivered", } --- Cargo transport status. @@ -1023,6 +1017,17 @@ end -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3(UnitName) + local vec3=nil --DCS#Vec3 + + -- First check if this group is loaded into a carrier + if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then + local unit=self.carrier.unit + if unit and unit:IsAlive()~=nil then + vec3=unit:GetVec3() + return vec3 + end + end + if self:IsExist() then local unit=nil --DCS#Unit @@ -1049,18 +1054,8 @@ end -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=nil --DCS#Vec3 + local vec3=self:GetVec3() or self.position --DCS#Vec3 - -- TODO: get vec3 of carrier group. move this stuff to GetVec3 - if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD then - -- Get the carrier position. - vec3=self.carrier.unit:GetVec3() - else - self:GetVec3() - end - - vec3=vec3 or self.position - if vec3 then self.coordinate=self.coordinate or COORDINATE:New(0,0,0) @@ -4646,16 +4641,7 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo queue:"..text) end end - - -- Loop over cargo queue and check if everything was delivered. - for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport - local delivered=self:_CheckDelivered(transport) - if delivered then - self:Delivered(transport) - end - end - + -- Check if there is anything in the queue. if not self.cargoTransport then self.cargoTransport=self:_GetNextCargoTransport() @@ -4696,12 +4682,12 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsLoading() then - -- Debug info. - self:I(self.lid.."Loading...") - -- Set loading time stamp. --TODO: Check max loading time. If exceeded ==> abort transport. - self.Tloading=self.Tloading or Time + self.Tloading=self.Tloading or Time + + -- Debug info. + self:I(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -4763,6 +4749,15 @@ function OPSGROUP:_CheckCargoTransport() end end + + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + local delivered=self:_CheckDelivered(transport) + if delivered then + self:Delivered(transport) + end + end return self end @@ -4773,6 +4768,8 @@ end -- @param #OPSGROUP.Element CarrierElement The element of the carrier. function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) + --TODO: Check group is not already in cargobay of this carrier or any other carrier. + -- Cargo weight. local weight=CargoGroup:GetWeightTotal() @@ -4789,6 +4786,7 @@ end -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. -- @param #OPSGROUP.Element CarrierElement The element of the carrier. +-- @return #boolean If `true`, cargo could be removed. function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) if self.cargoBay[CargoGroup.groupname] then @@ -4799,11 +4797,12 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) -- Reduce carrier weight. local weight=CargoGroup:GetWeightTotal() self:RedWeightCargo(CargoGroup.carrier.name, weight) - - else - env.error("ERROR: Group is not in cargo bay. Cannot remove it!") + + return true end + env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") + return false end --- Get cargo transport from cargo queue. @@ -4840,7 +4839,7 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4887,11 +4886,11 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return #OPSGROUP.CargoTransport Cargo transport. +-- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) + local cargotransport=OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) if cargotransport then @@ -4906,6 +4905,19 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo return cargotransport end +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. +function OPSGROUP:AddOpsTransport(OpsTransport) + + -- Set state to SCHEDULED. + OpsTransport.status=OPSTRANSPORT.Status.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, OpsTransport) + +end + --- Delete a cargo transport assignment from the cargo queue -- @param #OPSGROUP self -- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport do be deleted. @@ -5468,7 +5480,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) env.info("FF loaded") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5661,12 +5673,12 @@ function OPSGROUP:onafterUnloading(From, Event, To) local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) -- Random coordinate/heading in the zone. - local Coordinate=zoneCarrier:GetRandomCoordinate(20) + local Coordinate=zoneCarrier:GetRandomCoordinate(50) local Heading=math.random(0,359) -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) - self:Unload(cargo.opsgroup, Coordinate, Heading) + self:Unload(cargo.opsgroup, Coordinate, nil, Heading) else env.info("FF unload cargo Inactive "..cargo.opsgroup:GetName()) self:Unload(cargo.opsgroup) @@ -5700,9 +5712,9 @@ end -- @param #string To To state. -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. --- @param #number Heading Heading of group. -- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. -function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) +-- @param #number Heading (Optional) Heading of group in degrees. Default is random heading for each unit. +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) -- Remove group from carrier bay. self:_DelCargobay(OpsGroup) @@ -5713,10 +5725,6 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - -- No carrier. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil - if Coordinate then --- @@ -5776,7 +5784,11 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, end -- Trigger "Disembarked" event. - OpsGroup:Disembarked() + OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) + + -- No carrier any more. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil end @@ -5790,7 +5802,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:I(self.lid.."Cargo unloaded..") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5801,13 +5813,13 @@ function OPSGROUP:onafterUnloaded(From, Event, To) if not delivered then -- Pickup the next batch. - self:I(self.lid.."Still cargo left ==> pickup") + self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") self:Pickup(self.cargoTransport.pickupzone) else -- Everything delivered. - self:I(self.lid.."Still ALL unloaded ==> delivered") + self:I(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)") self:Delivered(self.cargoTransport) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua new file mode 100644 index 000000000..2c3f452c6 --- /dev/null +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -0,0 +1,209 @@ +--- **Ops** - Troop transport assignment of OPS groups. +-- +-- ## Main Features: +-- +-- * Patrol waypoints *ad infinitum* +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- == +-- +-- @module Ops.OpsTransport +-- @image OPS_OpsTransport.png + + +--- OPSTRANSPORT class. +-- @type OPSTRANSPORT +-- @field #string ClassName Name of the class. +-- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. +-- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #number importance Importance of this transport. Smaller=higher. +-- @field #number Tstart Start time in *abs.* seconds. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. +-- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. +-- @extends Core.Fsm#FSM + +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) +-- +-- # The OPSTRANSPORT Concept +-- +-- This class enhances naval groups. +-- +-- @field #OPSTRANSPORT +OPSTRANSPORT = { + ClassName = "OPSTRANSPORT", + verbose = 1, + cargos = {}, +} + +--- Cargo transport status. +-- @type OPSTRANSPORT.Status +-- @field #string PLANNING Planning state. +-- @field #string SCHEDULED Transport is scheduled in the cargo queue. +-- @field #string EXECUTING Transport is being executed. +-- @field #string DELIVERED Transport was delivered. +OPSTRANSPORT.Status={ + PLANNING="planning", + SCHEDULED="scheduled", + EXECUTING="executing", + DELIVERED="delivered", +} + +_OPSTRANSPORTID=0 + +--- Army Group version. +-- @field #string version +OPSTRANSPORT.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. Essential input are the troops that should be transported and the zones where the troops are picked up and deployed. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + + -- Set some string id for output to DCS.log file. + self.lid=string.format("OPSTRANSPORT %s --> %s | ", Pickupzone:GetName(), Deployzone:GetName()) + + _OPSTRANSPORTID=_OPSTRANSPORTID+1 + + self.uid=_OPSTRANSPORTID + self.status=OPSTRANSPORT.Status.PLANNING + self.pickupzone=Pickupzone + self.deployzone=Deployzone + self.embarkzone=Pickupzone + self.disembarkzone=Deployzone + self.prio=50 + self.importance=nil + self.Tstart=timer.getAbsTime() + self.carrierGroup=nil + self.cargos={} + + + -- Check type of GroupSet provided. + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + + -- We got a single GROUP or OPSGROUP objectg. + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) + table.insert(self.cargos, cargo) + end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) then + table.insert(self.cargos, cargo) + end + + end + end + + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + self:I(self.lid..text) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) + self.embarkzone=EmbarkZone or self.pickupzone + return self +end + + +--- Create a cargo group data structure. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP group The GROUP object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + local opsgroup=nil + + if group:IsInstanceOf("OPSGROUP") then + opsgroup=group + else + + opsgroup=_DATABASE:GetOpsGroup(group) + + if not opsgroup then + if group:IsAir() then + opsgroup=FLIGHTGROUP:New(group) + elseif group:IsShip() then + opsgroup=NAVYGROUP:New(group) + else + opsgroup=ARMYGROUP:New(group) + end + else + --env.info("FF found opsgroup in createcargo") + end + + end + + local cargo={} --#OPSGROUP.CargoGroup + + cargo.opsgroup=opsgroup + cargo.delivered=false + cargo.status="Unknown" + cargo.pickupzone=Pickupzone + cargo.deployzone=Deployzone + + return cargo +end