This commit is contained in:
Frank
2022-12-20 23:51:18 +01:00
parent 0adca414ce
commit 30e6542887
9 changed files with 1311 additions and 342 deletions

View File

@@ -30,6 +30,9 @@
-- @field #number Nred Number of red units in the zone.
-- @field #number Nblu Number of blue units in the zone.
-- @field #number Nnut Number of neutral units in the zone.
-- @field #number Tred Threat level of red units in the zone.
-- @field #number Tblu Threat level of blue units in the zone.
-- @field #number Tnut Threat level of neutral units in the zone.
-- @field #number TminCaptured Time interval in seconds how long an attacker must have troops inside the zone to capture.
-- @field #number Tcaptured Time stamp (abs.) when the attacker destroyed all owning troops.
-- @field #table ObjectCategories Object categories for the scan.
@@ -43,6 +46,10 @@
-- @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.
-- @field #number nunitsCapture Number of units necessary to capture a zone.
-- @field #number threatlevelCapture Threat level necessary to capture a zone.
-- @field Core.Set#SET_UNIT ScanUnitSet Set of scanned units.
-- @field Core.Set#SET_GROUP ScanGroupSet Set of scanned groups.
-- @extends Core.Fsm#FSM
--- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson
@@ -64,6 +71,9 @@ OPSZONE = {
Nred = 0,
Nblu = 0,
Nnut = 0,
Tred = 0,
Tblu = 0,
Tnut = 0,
chiefs = {},
Missions = {},
}
@@ -76,7 +86,7 @@ OPSZONE = {
--- OPSZONE class version.
-- @field #string version
OPSZONE.version="0.3.1"
OPSZONE.version="0.3.2"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -100,10 +110,10 @@ OPSZONE.version="0.3.1"
-- @param #number CoalitionOwner Initial owner of the coaliton. Default `coalition.side.NEUTRAL`.
-- @return #OPSZONE self
-- @usage
-- myopszone = OPSZONE:New(ZONE:FindByName("OpsZoneOne"),coalition.side.RED) -- base zone from the mission editor
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("OpsZoneTwo",mycoordinate:GetVec2(),5000),coalition.side.BLUE) -- radius zone of 5km at a coordinate
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("Batumi")) -- airbase zone from Batumi Airbase, ca 2500m radius
-- myopszone = OPSZONE:New(ZONE_AIRBASE:New("Batumi",6000),coalition.side.BLUE) -- airbase zone from Batumi Airbase, but with a specific radius of 6km
-- myopszone = OPSZONE:New(ZONE:FindByName("OpsZoneOne"), coalition.side.RED) -- base zone from the mission editor
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("OpsZoneTwo", mycoordinate:GetVec2(),5000),coalition.side.BLUE) -- radius zone of 5km at a coordinate
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("Batumi")) -- airbase zone from Batumi Airbase, ca 2500m radius
-- myopszone = OPSZONE:New(ZONE_AIRBASE:New("Batumi",6000),coalition.side.BLUE) -- airbase zone from Batumi Airbase, but with a specific radius of 6km
--
function OPSZONE:New(Zone, CoalitionOwner)
@@ -151,6 +161,11 @@ function OPSZONE:New(Zone, CoalitionOwner)
self.zoneName=Zone:GetName()
self.zoneRadius=Zone:GetRadius()
self.Missions = {}
self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone})
self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone})
-- Add to database.
_DATABASE:AddOpsZone(self)
-- Current and previous owners.
self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL
@@ -165,9 +180,6 @@ function OPSZONE:New(Zone, CoalitionOwner)
self.ownerPrevious=self.airbase:GetCoalition()
end
-- Set time to capture.
self:SetTimeCapture()
-- Set object categories.
self:SetObjectCategories()
self:SetUnitCategories()
@@ -176,6 +188,11 @@ function OPSZONE:New(Zone, CoalitionOwner)
self:SetDrawZone()
self:SetMarkZone(true)
-- Default capture parameters.
self:SetCaptureTime()
self:SetCaptureNunits()
self:SetCaptureThreatlevel()
-- Status timer.
self.timerStatus=TIMER:New(OPSZONE.Status, self)
@@ -186,6 +203,8 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Empty") -- Start FSM.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "Evaluated", "*") -- Evaluation done.
self:AddTransition("*", "Captured", "Guarded") -- Zone was captured.
@@ -220,6 +239,23 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Evaluated".
-- @function [parent=#OPSZONE] Evaluated
-- @param #OPSZONE self
--- Triggers the FSM event "Evaluated" after a delay.
-- @function [parent=#OPSZONE] __Evaluated
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
--- On after "Evaluated" event.
-- @function [parent=#OPSZONE] OnAfterEvaluated
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Triggers the FSM event "Captured".
-- @function [parent=#OPSZONE] Captured
-- @param #OPSZONE self
@@ -369,27 +405,27 @@ function OPSZONE:SetUnitCategories(Categories)
return self
end
--- Set threat level threshold that the defending units must have to hold a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to hold a zone as they do not pose a threat.
--- Set threat level threshold that the offending units must have to capture a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat.
-- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0.
-- @param #number Threatlevel Threat level threshold. Default 0.
-- @return #OPSZONE self
function OPSZONE:SetThreatlevelDefinding(Threatlevel)
function OPSZONE:SetCaptureThreatlevel(Threatlevel)
self.threatlevelDefending=Threatlevel or 0
self.threatlevelCapture=Threatlevel or 0
return self
end
--- Set threat level threshold that the offending units must have to capture a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat.
--- Set how many units must be present in a zone to capture it. By default, one unit is enough.
-- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0.
-- @param #number Nunits Number of units. Default 1.
-- @return #OPSZONE self
function OPSZONE:SetThreatlevelOffending(Threatlevel)
function OPSZONE:SetCaptureNunits(Nunits)
self.threatlevelOffending=Threatlevel or 0
Nunits=Nunits or 1
self.nunitsCapture=Nunits
return self
end
@@ -399,7 +435,7 @@ end
-- @param #OPSZONE self
-- @param #number Tcapture Time in seconds. Default 0.
-- @return #OPSZONE self
function OPSZONE:SetTimeCapture(Tcapture)
function OPSZONE:SetCaptureTime(Tcapture)
self.TminCaptured=Tcapture or 0
@@ -479,6 +515,21 @@ function OPSZONE:GetCoordinate()
return coordinate
end
--- Get scanned units inside the zone.
-- @param #OPSZONE self
-- @return Core.Set#SET_UNIT Set of units inside the zone.
function OPSZONE:GetScannedUnitSet()
return self.ScanUnitSet
end
--- Get scanned groups inside the zone.
-- @param #OPSZONE self
-- @return Core.Set#SET_GROUP Set of groups inside the zone.
function OPSZONE:GetScannedGroupSet()
return self.ScanGroupSet
end
--- Returns a random coordinate in the zone.
-- @param #OPSZONE self
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
@@ -753,11 +804,7 @@ function OPSZONE:onafterEmpty(From, Event, To)
-- Debug info.
self:T(self.lid..string.format("Zone is empty EVENT"))
-- Inform chief.
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
chief:ZoneEmpty(self)
end
end
@@ -771,17 +818,7 @@ function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition)
-- Debug info.
self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition)))
-- Inform chief.
if AttackerCoalition then
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
if chief.coalition~=AttackerCoalition then
chief:ZoneAttacked(self)
end
end
end
end
--- On after "Defeated" event.
@@ -806,19 +843,24 @@ end
-- @param #string Event Event.
-- @param #string To To state.
function OPSZONE:onenterGuarded(From, Event, To)
if From~=To then
-- Debug info.
self:T(self.lid..string.format("Zone is guarded"))
-- Debug info.
self:T(self.lid..string.format("Zone is guarded"))
-- Not attacked any more.
self.Tattacked=nil
if self.drawZone then
self.zone:UndrawZone()
-- Not attacked any more.
self.Tattacked=nil
if self.drawZone then
local color=self:_GetZoneColor()
self.zone:UndrawZone()
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
end
@@ -828,26 +870,43 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function OPSZONE:onenterAttacked(From, Event, To)
-- Debug info.
self:T(self.lid..string.format("Zone is Attacked"))
-- @param #number AttackerCoalition Coalition of the attacking ground troops.
function OPSZONE:onenterAttacked(From, Event, To, AttackerCoalition)
-- Time stamp when the attack started.
self.Tattacked=timer.getAbsTime()
if From~="Attacked" then
-- Draw zone?
if self.drawZone then
self.zone:UndrawZone()
-- Color.
local color={1, 204/255, 204/255}
-- Draw zone.
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
-- Debug info.
self:T(self.lid..string.format("Zone is Attacked"))
self:_CleanMissionTable()
-- Set time stamp.
self.Tattacked=timer.getAbsTime()
-- Inform chief.
if AttackerCoalition then
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
if chief.coalition~=AttackerCoalition then
chief:ZoneAttacked(self)
end
end
end
-- Draw zone?
if self.drawZone then
self.zone:UndrawZone()
-- Color.
local color={1, 204/255, 204/255}
-- Draw zone.
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
self:_CleanMissionTable()
end
end
--- On enter "Empty" event.
@@ -857,17 +916,27 @@ end
-- @param #string To To state.
function OPSZONE:onenterEmpty(From, Event, To)
-- Debug info.
self:T(self.lid..string.format("Zone is empty now"))
if From~=To then
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
end
-- Debug info.
self:T(self.lid..string.format("Zone is empty now"))
-- Inform chief.
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
chief:ZoneEmpty(self)
end
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -892,6 +961,13 @@ function OPSZONE:Scan()
local Nred=0
local Nblu=0
local Nnut=0
local Tred=0
local Tblu=0
local Tnut=0
self.ScanGroupSet:Clear(false)
self.ScanUnitSet:Clear(false)
--- Function to evaluate the world search
local function EvaluateZone(_ZoneObject)
@@ -939,13 +1015,35 @@ function OPSZONE:Scan()
-- Get Coalition.
local Coalition=DCSUnit:getCoalition()
local tl=0
local unit=UNIT:Find(DCSUnit)
if unit then
-- Threat level of unit.
tl=unit:GetThreatLevel()
-- Add unit to set.
self.ScanUnitSet:AddUnit(unit)
-- Get group of unit.
local group=unit:GetGroup()
if group then
self.ScanGroupSet:AddGroup(group, true)
end
end
-- Increase counter.
if Coalition==coalition.side.RED then
Nred=Nred+1
Tred=Tred+tl
elseif Coalition==coalition.side.BLUE then
Nblu=Nblu+1
Tblu=Tblu+tl
elseif Coalition==coalition.side.NEUTRAL then
Nnut=Nnut+1
Tnut=Tnut+tl
end
-- Debug info.
@@ -1014,6 +1112,10 @@ function OPSZONE:Scan()
self.Nred=Nred
self.Nblu=Nblu
self.Nnut=Nnut
self.Tblu=Tblu
self.Tred=Tred
self.Tnut=Tnut
return self
end
@@ -1028,6 +1130,30 @@ function OPSZONE:EvaluateZone()
local Nblu=self.Nblu
local Nnut=self.Nnut
local Tnow=timer.getAbsTime()
--- Capture
-- @param #number coal Coaltion capturing.
local function captured(coal)
-- Blue captured red zone.
if not self.airbase then
-- Set time stamp if it does not exist.
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coal)
self.Tcaptured=nil
end
end
end
if self:IsRed() then
---
@@ -1038,43 +1164,16 @@ function OPSZONE:EvaluateZone()
-- No red units in red zone any more.
if Nblu>0 then
-- Blue captured red zone.
if not self.airbase then
local Tnow=timer.getAbsTime()
-- Set time stamp if it does not exist.
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coalition.side.BLUE)
self.Tcaptured=nil
end
end
elseif Nnut>0 and self.neutralCanCapture then
if Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
-- Blue captued red zone.
captured(coalition.side.BLUE)
elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
-- Neutral captured red zone.
if not self.airbase then
local Tnow=timer.getAbsTime()
-- Set time stamp if it does not exist.
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coalition.side.NEUTRAL)
self.Tcaptured=nil
end
end
else
-- Red zone is now empty (but will remain red).
if not self:IsEmpty() then
self:Empty()
end
captured(coalition.side.NEUTRAL)
end
else
@@ -1117,21 +1216,16 @@ function OPSZONE:EvaluateZone()
-- No blue units in blue zone any more.
if Nred>0 then
if Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
-- Red captured blue zone.
if not self.airbase then
self:Captured(coalition.side.RED)
end
elseif Nnut>0 and self.neutralCanCapture then
captured(coalition.side.RED)
elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
-- Neutral captured blue zone.
if not self.airbase then
self:Captured(coalition.side.NEUTRAL)
end
else
-- Blue zone is empty now.
if not self:IsEmpty() then
self:Empty()
end
captured(coalition.side.NEUTRAL)
end
else
@@ -1152,7 +1246,7 @@ function OPSZONE:EvaluateZone()
self:Defeated(coalition.side.RED)
elseif self:IsEmpty() then
-- Blue units left zone and returned (or from initial Empty state).
self:Guarded()
self:Guarded()
end
end
@@ -1183,21 +1277,12 @@ function OPSZONE:EvaluateZone()
self:Attacked()
end
self.isContested=true
elseif Nred>0 then
elseif Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
-- Red captured neutral zone.
if not self.airbase then
self:Captured(coalition.side.RED)
end
elseif Nblu>0 then
captured(coalition.side.RED)
elseif Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
-- Blue captured neutral zone.
if not self.airbase then
self:Captured(coalition.side.BLUE)
end
else
-- Neutral zone is empty now.
if not self:IsEmpty() then
self:Empty()
end
captured(coalition.side.BLUE)
end
--end
@@ -1206,6 +1291,11 @@ function OPSZONE:EvaluateZone()
self:E(self.lid.."ERROR: Unknown coaliton!")
end
-- No units of any coalition in zone any more ==> Empty!
if Nblu==0 and Nred==0 and Nnut==0 and (not self:IsEmpty()) then
self:Empty()
end
-- Finally, check airbase coalition
if self.airbase then
@@ -1219,6 +1309,9 @@ function OPSZONE:EvaluateZone()
end
end
-- Trigger event.
self:Evaluated()
end
@@ -1328,7 +1421,7 @@ function OPSZONE:_UpdateMarker()
end
--- Get marker text
--- Get marker text.
-- @param #OPSZONE self
-- @return #string Marker text.
function OPSZONE:_GetMarkerText()
@@ -1337,8 +1430,10 @@ function OPSZONE:_GetMarkerText()
local prevowner=UTILS.GetCoalitionName(self.ownerPrevious)
-- Get marker text.
local text=string.format("%s: Owner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d, Red=%d, Neutral=%d",
self.zoneName, owner, prevowner, self:GetState(), tostring(self:IsContested()), self.Nblu, self.Nred, self.Nnut)
local text=string.format("%s [N=%d, TL=%d T=%d]:\nOwner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d [TL=%d]\nRed=%d [TL=%d]\nNeutral=%d [TL=%d]",
self.zoneName, self.nunitsCapture or 0, self.threatlevelCapture or 0, self.TminCaptured or 0,
owner, prevowner, self:GetState(), tostring(self:IsContested()),
self.Nblu, self.Tblu, self.Nred, self.Tred, self.Nnut, self.Tnut)
return text
end