diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index becd99218..d720e8803 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1201,6 +1201,8 @@ AIRBOSS = { NmaxSection = nil, NmaxStack = nil, handleai = nil, + xtVoiceOvers = nil, + xtVoiceOversAI = nil, tanker = nil, Corientation = nil, Corientlast = nil, @@ -1900,6 +1902,12 @@ function AIRBOSS:New(carriername, alias) -- Set AI handling On. self:SetHandleAION() + -- No extra voiceover/calls from player by default + self:SetExtraVoiceOvers(false) + + -- No extra voiceover/calls from AI by default + self:SetExtraVoiceOversAI(false) + -- Airboss is a nice guy. self:SetAirbossNiceGuy() @@ -3215,6 +3223,24 @@ function AIRBOSS:SetHandleAION() return self end +--- Will play the inbound calls, commencing, initial, etc. from the player when requesteing marshal +-- @param #AIRBOSS self +-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON) +-- @return #AIRBOSS self +function AIRBOSS:SetExtraVoiceOvers(status) + self.xtVoiceOvers=status + return self +end + +--- Will simulate the inbound call, commencing, initial, etc from the AI when requested by Airboss +-- @param #AIRBOSS self +-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON) +-- @return #AIRBOSS self +function AIRBOSS:SetExtraVoiceOversAI(status) + self.xtVoiceOversAI=status + return self +end + --- Do not handle AI aircraft. -- @param #AIRBOSS self -- @return #AIRBOSS self @@ -5299,6 +5325,48 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=1.95, }, + MARSHAL={ + file="PILOT-Marshal", + suffix="ogg", + loud=false, + subtitle="", + duration=0.50, + }, + MARKINGMOMS={ + file="PILOT-MarkingMoms", + suffix="ogg", + loud=false, + subtitle="", + duration=0.90, + }, + FOR={ + file="PILOT-For", + suffix="ogg", + loud=false, + subtitle="", + duration=0.29, + }, + ANGELS={ + file="PILOT-Angels", + suffix="ogg", + loud=false, + subtitle="", + duration=0.50, + }, + STATE={ + file="PILOT-State", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + COMMENCING={ + file="PILOT-Commencing", + suffix="ogg", + loud=false, + subtitle="", + duration=0.45, + }, } ------------------- @@ -6174,6 +6242,12 @@ function AIRBOSS:_ClearForLanding(flight) -- Cleared for Case X recovery. self:_MarshalCallClearedForRecovery(flight.onboard, flight.case) + -- Voice over of the commencing simulated call from AI + if self.xtVoiceOversAI then + local leader = flight.group:GetUnits()[1] + self:_CommencingCall(leader, flight.onboard) + end + else -- Cleared for Case X recovery. @@ -6545,6 +6619,11 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Check if flight is already in Marshal queue. if not self:_InQueue(self.Qmarshal,flight.group) then + -- Simulate inbound call + if self.xtVoiceOversAI then + local leader = flight.group:GetUnits()[1] + self:_MarshallInboundCall(leader, flight.onboard) + end -- Add group to marshal stack queue. self:_AddMarshalGroup(flight, nstack) end @@ -15722,6 +15801,85 @@ function AIRBOSS:_Number2Radio(radio, number, delay, interval, pilotcall) return wait end +--- Aircraft request marshal (Inbound call both for players and AI). +-- @param #AIRBOSS self +-- @return Wrapper.Unit#UNIT Unit of player or nil. +-- @param #string modex Tail number. +function AIRBOSS:_MarshallInboundCall(unit, modex) + + -- Calculate + local vectorCarrier = self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate()) + local bearing = UTILS.Round(unit:GetCoordinate():GetAngleDegrees( vectorCarrier ), 0) + local distance = UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0) + local angels = UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0) + local state = UTILS.Round(self:_GetFuelState(unit)/1000,1) + + -- Pilot: "Marshall, [modex], marking mom's [bearing] for [distance], angels [XX], state [X.X]" + local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f", modex, bearing, distance, angels, state) + -- Debug message. + self:I(self.lid..text) + + -- Fuel state. + local FS=UTILS.Split(string.format("%.1f", state), ".") + + -- Create new call to display complete subtitle. + local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper()) + + -- CLICK! + self:RadioTransmission(self.MarshalRadio, inboundcall) + -- Marshal .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARSHAL, nil, nil, nil, nil, true) + -- Modex.. + self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true) + -- Marking Mom's, + self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARKINGMOMS, nil, nil, nil, nil, true) + -- Bearing .. + self:_Number2Radio(self.MarshalRadio, tostring(bearing), nil, nil, true) + -- For .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.FOR, nil, nil, nil, nil, true) + -- Distance .. + self:_Number2Radio(self.MarshalRadio, tostring(distance), nil, nil, true) + -- Angels .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.ANGELS, nil, nil, nil, nil, true) + -- Angels Number .. + self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, nil, true) + -- State .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.STATE, nil, nil, nil, nil, true) + -- X.. + self:_Number2Radio(self.MarshalRadio, FS[1], nil, nil, true) + -- Point.. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.POINT, nil, nil, nil, nil, true) + -- Y. + self:_Number2Radio(self.MarshalRadio, FS[2], nil, nil, true) + -- CLICK! + self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true) + +end + +--- Aircraft commencing call (both for players and AI). +-- @param #AIRBOSS self +-- @return Wrapper.Unit#UNIT Unit of player or nil. +-- @param #string modex Tail number. +function AIRBOSS:_CommencingCall(unit, modex) + + -- Pilot: "[modex], commencing" + local text=string.format("%s, commencing", modex) + -- Debug message. + self:I(self.lid..text) + + -- Create new call to display complete subtitle. + local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper()) + + -- Click + self:RadioTransmission(self.MarshalRadio, commencingCall) + -- Modex.. + self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true) + -- Commencing + self:RadioTransmission(self.MarshalRadio, self.PilotCall.COMMENCING, nil, nil, nil, nil, true) + -- CLICK! + self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true) + +end --- AI aircraft calls the ball. -- @param #AIRBOSS self @@ -16478,13 +16636,18 @@ function AIRBOSS:_RequestMarshal(_unitName) -- Get player unit and name. local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - + -- Check if we have a unit which is a player. if _unit and _playername then local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then + -- Voice over of inbound call (regardless of airboss rejecting it or not) + if self.xtVoiceOvers then + self:_MarshallInboundCall(_unit, playerData.onboard) + end + -- Check if player is in CCA local inCCA=playerData.unit:IsInZone(self.zoneCCA) @@ -16726,13 +16889,18 @@ function AIRBOSS:_RequestCommence(_unitName) -- Get player unit and name. local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - + -- Check if we have a unit which is a player. if _unit and _playername then local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then - + + -- Voice over of Commencing call (regardless of Airboss will rejected or not) + if self.xtVoiceOvers then + self:_CommencingCall(_unit, playerData.onboard) + end + -- Check if unit is in CCA. local text="" local cleared=false diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 9bdfb59a0..32553dcb7 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -65,7 +65,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.7.0" +ARMYGROUP.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -679,7 +679,7 @@ function ARMYGROUP:Status() -- Info text. local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d, Cargo=%.1f", fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading or 0, ammo.Total, cargo) - self:I(self.lid..text) + self:T(self.lid..text) end @@ -720,7 +720,7 @@ function ARMYGROUP:Status() if #self.elements==0 then text=text.." none!" end - self:I(self.lid..text) + self:T(self.lid..text) end --- @@ -732,7 +732,7 @@ function ARMYGROUP:Status() -- If we found a group, we engage it. if targetgroup then - self:I(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) + self:T(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) self:EngageTarget(targetgroup) end @@ -925,13 +925,13 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Next waypoint. local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint - + self:T({wp}) -- Speed. if Speed then wp.speed=UTILS.KnotsToMps(Speed) else - -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if wp.speed<0.1 then --self.adinfinitum and + -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. + if wp.speed<0.1 then --self.adinfinitum and wp.speed=UTILS.KmphToMps(self.speedCruise) end end @@ -951,7 +951,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Add "On Road" waypoint in between. local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Insert road waypoint. table.insert(waypoints, wproad) end @@ -972,21 +972,32 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Current set speed in m/s. self.speedWp=wp.speed - local formation0=wp.action==ENUMS.Formation.Vehicle.OnRoad and ENUMS.Formation.Vehicle.OffRoad or wp.action + local formation0=wp.action==ENUMS.Formation.Vehicle.OnRoad and ENUMS.Formation.Vehicle.OnRoad or wp.action -- Current point. local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) table.insert(waypoints, 1, current) -- Insert a point on road. - if wp.action==ENUMS.Formation.Vehicle.OnRoad then - local current=self:GetClosestRoad():WaypointGround(UTILS.MpsToKmph(self.speedWp), ENUMS.Formation.Vehicle.OnRoad) - table.insert(waypoints, 2, current) + if wp.action==ENUMS.Formation.Vehicle.OnRoad and (wp.coordinate or wp.roadcoord) then + --local current=self:GetClosestRoad():WaypointGround(UTILS.MpsToKmph(self.speedWp), ENUMS.Formation.Vehicle.OnRoad) + local wptable,length,valid=self:GetCoordinate():GetPathOnRoad(wp.coordinate or wp.roadcoord,true,false,false,false) or {} + local count = 2 + if valid then + for _,_coord in ipairs(wptable) do + local current = _coord:WaypointGround(UTILS.MpsToKmph(self.speedWp), ENUMS.Formation.Vehicle.OnRoad) + table.insert(waypoints, count, current) + count=count+1 + end + else + current=self:GetClosestRoad():WaypointGround(UTILS.MpsToKmph(self.speedWp), ENUMS.Formation.Vehicle.OnRoad) + table.insert(waypoints, count, current) + end end - -- Debug output. if self.verbose>=10 then + --if true then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or -1, wp.type, wp.speed, wp.alt, wp.action) @@ -1083,14 +1094,14 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterOutOfAmmo(From, Event, To) - self:I(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) -- Get current task. local task=self:GetTaskCurrent() if task then if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then - self:I(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) + self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) self:TaskCancel(task) end end @@ -1169,7 +1180,7 @@ end function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) -- Debug info. - self:I(self.lid..string.format("Group send to rearm")) + self:T(self.lid..string.format("Group send to rearm")) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -1188,7 +1199,7 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterRearmed(From, Event, To) - self:I(self.lid.."Group rearmed") + self:T(self.lid.."Group rearmed") -- Check group done. self:_CheckGroupDone(1) @@ -1216,7 +1227,7 @@ function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) else -- Debug info. - self:I(self.lid..string.format("RTZ to Zone %s", zone:GetName())) + self:T(self.lid..string.format("RTZ to Zone %s", zone:GetName())) local Coordinate=zone:GetRandomCoordinate() @@ -1552,11 +1563,11 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. --- @param #number Formation Formation the group will use. +-- @param #string Formation Formation the group will use. -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - + self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation) or "none")) -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) @@ -1565,8 +1576,20 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - - -- Create a Naval waypoint. + + -- Formation + + if not Formation then + if self.formationPerma then + Formation = self.formationPerma + elseif self.option.Formation then + Formation = self.option.Formation + else + Formation = "On Road" + end + end + + -- Create a Ground waypoint. local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation) -- Create waypoint data table. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index fced99dd5..721248ed8 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -922,7 +922,8 @@ function AUFTRAG:NewORBIT(Coordinate, Altitude, Speed, Heading, Leg) mission:_TargetFromObject(Coordinate) - mission.orbitSpeed = UTILS.KnotsToMps(Speed or 350) + mission.orbitSpeed = UTILS.KnotsToKmph(Speed or 350) + mission.missionSpeed = UTILS.KnotsToKmph(Speed or 350) if Heading and Leg then mission.orbitHeading=Heading diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index d566ea394..ca643da08 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -247,7 +247,7 @@ CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r3" +CSAR.version="0.1.12r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1122,9 +1122,9 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local dist = UTILS.MetersToNM(self.autosmokedistance) disttext = string.format("%.0fnm",dist) end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index b449dc95c..c6997e083 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -5,7 +5,7 @@ -- * Automatic target engagement based on detection network -- * Define multiple border, conflict and attack zones -- * Define strategic "capture" zones --- * Set stragegy of chief from passive to agressive +-- * Set strategy of chief from passive to agressive -- * Manual target engagement via AUFTRAG and TARGET classes -- * Add AIRWINGS, BRIGADES and FLEETS as resources -- * Seamless air-to-air, air-to-ground, ground-to-ground dispatching @@ -101,7 +101,7 @@ -- -- # Strategic (Capture) Zones -- --- Strategically important zones, which should be captured can be added via the @{#CHIEF.AddStrateticZone}() function. +-- Strategically important zones, which should be captured can be added via the @{#CHIEF.AddStrategicZone}() function. -- -- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS mission is lauchned. -- @@ -174,11 +174,13 @@ CHIEF.Strategy = { -- @field #number importance Importance -- @field Ops.Auftrag#AUFTRAG missionPatrol Patrol mission. -- @field Ops.Auftrag#AUFTRAG missionCAS CAS mission. +-- @field Ops.Auftrag#AUFTRAG missionPatrol Patrol mission. +-- @field Ops.Auftrag#AUFTRAG missionARTY Artillery mission. --- CHIEF class version. -- @field #string version -CHIEF.version="0.0.1" +CHIEF.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -316,7 +318,7 @@ function CHIEF:New(Coalition, AgentSet, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Strategy New stragegy. + -- @param #string Strategy New strategy. --- Triggers the FSM event "MissionAssign". @@ -587,11 +589,11 @@ function CHIEF:GetDefcon(Defcon) return self.Defcon end ---- Set stragegy. +--- Set strategy. -- @param #CHIEF self --- @param #string Strategy Strategy. See @{#CHIEF.Stragegy}, e.g. `CHIEF.Strategy.DEFENSIVE` (default). +-- @param #string Strategy Strategy. See @{#CHIEF.strategy}, e.g. `CHIEF.Strategy.DEFENSIVE` (default). -- @return #CHIEF self -function CHIEF:SetStragety(Strategy) +function CHIEF:SetStrategy(Strategy) -- Trigger event if Strategy changed. if Strategy~=self.strategy then @@ -767,7 +769,7 @@ end -- @param #number Priority Priority. -- @param #number Importance Importance. -- @return #CHIEF self -function CHIEF:AddStrateticZone(OpsZone, Priority, Importance) +function CHIEF:AddStrategicZone(OpsZone, Priority, Importance) local stratzone={} --#CHIEF.StrategicZone @@ -1556,12 +1558,18 @@ function CHIEF:CheckTargetQueue() -- * target threatlevel -- * how many assets are still in stock -- * is it inside of our border + -- * add damping factor + local NassetsMin=1 local NassetsMax=1 - if target.threatlevel0>=8 then + local threat = target.threatlevel0 / target.N0 -- avg threatlevel + local NoUnits = target.N0 -- no of units in here + + --if target.threatlevel0>=8 then + if threat>=8 and NoUnits >=10 then NassetsMax=3 - elseif target.threatlevel0>=5 then + elseif threat>=5 then NassetsMax=2 else NassetsMax=1 @@ -1675,9 +1683,14 @@ function CHIEF:CheckOpsZoneQueue() if ownercoalition~=self.coalition and (stratzone.importance==nil or stratzone.importance<=vip) then -- Has a patrol mission? - local hasMissionPatrol=stratzone.missionPatrol and stratzone.missionPatrol:IsNotOver() or false + --local hasMissionPatrol=stratzone.missionPatrol and stratzone.missionPatrol:IsNotOver() or false + local hasMissionPatrol=stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.PATROLZONE) -- Has a CAS mission? - local hasMissionCAS=stratzone.missionCAS and stratzone.missionCAS:IsNotOver() or false + --local hasMissionCAS=stratzone.missionCAS and stratzone.missionCAS:IsNotOver() or false + local hasMissionCAS=stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.CAS) + -- Has a ARTY mission? + --local hasMissionARTY=stratzone.missionARTY and stratzone.missionARTY:IsNotOver() or false + local hasMissionARTY=stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.ARTY) -- Debug info. self:T(self.lid..string.format("Zone %s [%s] is owned by coalition %d", stratzone.opszone.zone:GetName(), stratzone.opszone:GetState(), ownercoalition)) @@ -1696,7 +1709,7 @@ function CHIEF:CheckOpsZoneQueue() self:T3(self.lid..string.format("Zone is empty ==> Recruit Patrol zone infantry assets")) -- Recruit ground assets that - local recruited=self:RecruitAssetsForZone(stratzone, AUFTRAG.Type.ONGUARD, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) + local recruited=self:RecruitAssetsForZone(stratzone, AUFTRAG.Type.ONGUARD, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY, GROUP.Attribute.GROUND_TANK}) -- Debug info. self:T(self.lid..string.format("Zone is empty ==> Recruit Patrol zone infantry assets=%s", tostring(recruited))) @@ -1724,6 +1737,19 @@ function CHIEF:CheckOpsZoneQueue() self:T(self.lid..string.format("Zone is NOT empty ==> Recruit CAS assets=%s", tostring(recruited))) end + if not hasMissionARTY then + + -- Debug message. + self:T3(self.lid..string.format("Zone is NOT empty ==> Recruit ARTY assets")) + + + -- Recruite CAS assets. + local recruited=self:RecruitAssetsForZone(stratzone, AUFTRAG.Type.ARTY, 1, 1) + + -- Debug message. + self:T(self.lid..string.format("Zone is NOT empty ==> Recruit ARTY assets=%s", tostring(recruited))) + + end end @@ -1738,17 +1764,31 @@ function CHIEF:CheckOpsZoneQueue() local ownercoalition=stratzone.opszone:GetOwner() -- Has a patrol mission? - local hasMissionPatrol=stratzone.missionPatrol and stratzone.missionPatrol:IsNotOver() or false + --local hasMissionPatrol=stratzone.missionPatrol and stratzone.missionPatrol:IsNotOver() or false + local hasMissionPATROL=stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.PATROLZONE) -- Has a CAS mission? - local hasMissionCAS=stratzone.missionCAS and stratzone.missionCAS:IsNotOver() or false + --local hasMissionCAS=stratzone.missionCAS and stratzone.missionCAS:IsNotOver() or false + local hasMissionCAS, CASMissions = stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.CAS) + local hasMissionARTY, ARTYMissions = stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.ARTY) if ownercoalition==self.coalition and stratzone.opszone:IsEmpty() and hasMissionCAS then -- Cancel CAS mission if zone is ours and no enemies are present. -- TODO: Might want to check if we still have CAS capable assets in stock?! - stratzone.missionCAS:Cancel() + --stratzone.missionCAS:Cancel() + for _,_auftrag in pairs(CASMissions) do + _auftrag:Cancel() + end end + if ownercoalition==self.coalition and hasMissionARTY then + -- Cancel CAS mission if zone is ours and no enemies are present. + -- TODO: Might want to check if we still have ARTY capable assets in stock?! + --stratzone.missionCAS:Cancel() + for _,_auftrag in pairs(ARTYMissions) do + _auftrag:Cancel() + end + end end @@ -1907,8 +1947,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) -- EWR - --table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) - table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) + --table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) elseif attribute==GROUP.Attribute.GROUND_AAA then @@ -2036,7 +2076,7 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa for _,_legion in pairs(self.commander.legions) do local legion=_legion --Ops.Legion#LEGION - -- Check that runway is operational. + -- Check that runway is operational.d local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true if legion:IsRunning() and Runway then @@ -2062,7 +2102,7 @@ end --- Recruit assets for a given OPS zone. -- @param #CHIEF self --- @param #CHIEF.StrategicZone StratZone The stratetic zone. +-- @param #CHIEF.StrategicZone StratZone The strategic zone. -- @param #string MissionType Mission Type. -- @param #number NassetsMin Min number of required assets. -- @param #number NassetsMax Max number of required assets. @@ -2151,7 +2191,8 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM -- Attach mission to ops zone. -- TODO: Need a better way! - StratZone.missionPatrol=mission + --StratZone.missionPatrol=mission + StratZone.opszone:_AddMission(self.coalition,MissionType,mission) return true else @@ -2162,8 +2203,34 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM elseif MissionType==AUFTRAG.Type.CAS then -- Create Patrol zone mission. - local mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone) + local caszone = StratZone.opszone.zone + local coord = caszone:GetCoordinate() + local height = UTILS.MetersToFeet(coord:GetLandHeight())+2000 + local mission=AUFTRAG:NewPATROLZONE(caszone) mission:SetEngageDetected(25, {"Ground Units", "Light armed ships", "Helicopters"}) + mission:SetWeaponExpend(AI.Task.WeaponExpend.ALL) + + -- Add assets to mission. + for _,asset in pairs(assets) do + mission:AddAsset(asset) + end + + -- Assign mission to legions. + self:MissionAssign(mission, legions) + + -- Attach mission to ops zone. + -- TODO: Need a better way! + --StratZone.missionCAS=mission + StratZone.opszone:_AddMission(self.coalition,MissionType,mission) + + return true + elseif MissionType==AUFTRAG.Type.ARTY then + + -- Create ARTY zone mission. + local TargetZone = StratZone.opszone.zone + local Target = TargetZone:GetCoordinate() + local Radius = TargetZone:GetRadius() + local mission=AUFTRAG:NewARTY(Target,120,Radius) -- Add assets to mission. for _,asset in pairs(assets) do @@ -2175,8 +2242,9 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM -- Attach mission to ops zone. -- TODO: Need a better way! - StratZone.missionCAS=mission - + --StratZone.missionARTY=mission + StratZone.opszone:_AddMission(self.coalition,MissionType,mission) + return true end @@ -2187,4 +2255,4 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index ef785f6eb..ad61ca2e2 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -517,7 +517,7 @@ end --- Add a CAP zone. -- @param #COMMANDER self --- @param Core.Zone#ZONE CapZone Zone. +-- @param Core.Zone#ZONE Zone CapZone Zone. -- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet. -- @param #number Speed Orbit speed in KIAS. Default 350 kts. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). @@ -542,7 +542,7 @@ end --- Add a GCICAP zone. -- @param #COMMANDER self --- @param Core.Zone#ZONE CapZone Zone. +-- @param Core.Zone#ZONE Zone CapZone Zone. -- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet. -- @param #number Speed Orbit speed in KIAS. Default 350 kts. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index fee913378..e802a0f87 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -39,6 +39,7 @@ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string markerText Text shown in the maker. -- @field #table chiefs Chiefs that monitor this zone. +-- @field #table Missions Missions that are attached to this OpsZone -- @extends Core.Fsm#FSM --- Be surprised! @@ -61,8 +62,14 @@ OPSZONE = { Nblu = 0, Nnut = 0, chiefs = {}, + Missions = {}, } +--- OPSZONE.MISSION +-- @type OPSZONE.MISSION +-- @field #number Coalition Coalition +-- @field #string Type Type of mission +-- @field Ops.Auftrag#AUFTRAG Mission The actual attached mission --- OPSZONE class version. -- @field #string version @@ -74,7 +81,7 @@ OPSZONE.version="0.2.0" -- TODO: Pause/unpause evaluations. -- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it. --- TODO: Can neutrals capture? No, since they are _neutral_! +-- DONE: Can neutrals capture? No, since they are _neutral_! -- TODO: Differentiate between ground attack and boming by air or arty. -- DONE: Capture airbases. -- DONE: Can statics capture or hold a zone? No, unless explicitly requested by mission designer. @@ -133,6 +140,7 @@ function OPSZONE:New(Zone, CoalitionOwner) self.zone=Zone self.zoneName=Zone:GetName() self.zoneRadius=Zone:GetRadius() + self.Missions = {} -- Current and previous owners. self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL @@ -713,7 +721,8 @@ function OPSZONE:onenterAttacked(From, Event, To) -- Draw zone. self.zone:DrawZone(nil, color, 1.0, color, 0.5) end - + + self:_CleanMissionTable() end --- On enter "Empty" event. @@ -1198,6 +1207,68 @@ function OPSZONE:_AddChief(Chief) end +--- Add an entry to the OpsZone mission table +-- @param #OPSZONE self +-- @param #number Coalition Coalition of type e.g. coalition.side.NEUTRAL +-- @param #string Type Type of mission, e.g. AUFTRAG.Type.CAS +-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag itself +-- @return #OPSZONE self +function OPSZONE:_AddMission(Coalition,Type,Auftrag) + + -- Add a mission + local entry = {} -- #OPSZONE.MISSION + entry.Coalition = Coalition or coalition.side.NEUTRAL + entry.Type = Type or "" + entry.Mission = Auftrag or nil + + table.insert(self.Missions,entry) + + return self +end + +--- Get the OpsZone mission table. #table of #OPSZONE.MISSION entries +-- @param #OPSZONE self +-- @return #table Missions +function OPSZONE:_GetMissions() + return self.Missions +end + +--- Add an entry to the OpsZone mission table +-- @param #OPSZONE self +-- @param #number Coalition Coalition of type e.g. coalition.side.NEUTRAL +-- @param #string Type Type of mission, e.g. AUFTRAG.Type.CAS +-- @return #boolean found True if we have that kind of mission, else false +-- @return #table Missions Table of Ops.Auftrag#AUFTRAG entries +function OPSZONE:_FindMissions(Coalition,Type) + -- search the table + local foundmissions = {} + local found = false + for _,_entry in pairs(self.Missions) do + local entry = _entry -- #OPSZONE.MISSION + if entry.Coalition == Coalition and entry.Type == Type and entry.Mission and entry.Mission:IsNotOver() then + table.insert(foundmissions,entry.Mission) + found = true + end + end + return found, foundmissions +end + +--- Housekeeping +-- @param #OPSZONE self +-- @return #OPSZONE self +function OPSZONE:_CleanMissionTable() + local missions = {} + for _,_entry in pairs(self.Missions) do + local entry = _entry -- #OPSZONE.MISSION + if entry.Mission and entry.Mission:IsNotOver() then + table.insert(missions,entry) + end + end + self.Missions = missions + return self +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------