mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
1374 lines
37 KiB
Lua
1374 lines
37 KiB
Lua
--- **Ops** - Target.
|
|
--
|
|
-- **Main Features:**
|
|
--
|
|
-- * Manages target, number alive, life points, damage etc.
|
|
-- * Events when targets are damaged or destroyed
|
|
-- * Various target objects: UNIT, GROUP, STATIC, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **funkyfranky**
|
|
-- @module Ops.Target
|
|
-- @image OPS_Target.png
|
|
|
|
|
|
--- TARGET class.
|
|
-- @type TARGET
|
|
-- @field #string ClassName Name of the class.
|
|
-- @field #number verbose Verbosity level.
|
|
-- @field #number uid Unique ID of the target.
|
|
-- @field #string lid Class id string for output to DCS log file.
|
|
-- @field #table targets Table of target objects.
|
|
-- @field #number targetcounter Running number to generate target object IDs.
|
|
-- @field #number life Total life points on last status update.
|
|
-- @field #number life0 Total life points of completely healthy targets.
|
|
-- @field #number threatlevel0 Initial threat level.
|
|
-- @field #number category Target category (Ground, Air, Sea).
|
|
-- @field #number N0 Number of initial target elements/units.
|
|
-- @field #number Ntargets0 Number of initial target objects.
|
|
-- @field #number Ndestroyed Number of target elements/units that were destroyed.
|
|
-- @field #number Ndead Number of target elements/units that are dead (destroyed or despawned).
|
|
-- @field #table elements Table of target elements/units.
|
|
-- @field #table casualties Table of dead element names.
|
|
-- @field #number prio Priority.
|
|
-- @field #number importance Importance.
|
|
-- @field #boolean isDestroyed If true, target objects were destroyed.
|
|
-- @extends Core.Fsm#FSM
|
|
|
|
--- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower
|
|
--
|
|
-- ===
|
|
--
|
|
-- # The TARGET Concept
|
|
--
|
|
-- Define a target of your mission and monitor its status. Events are triggered when the target is damaged or destroyed.
|
|
--
|
|
-- A target can consist of one or multiple "objects".
|
|
--
|
|
--
|
|
-- @field #TARGET
|
|
TARGET = {
|
|
ClassName = "TARGET",
|
|
verbose = 0,
|
|
lid = nil,
|
|
targets = {},
|
|
targetcounter = 0,
|
|
life = 0,
|
|
life0 = 0,
|
|
N0 = 0,
|
|
Ntargets0 = 0,
|
|
Ndestroyed = 0,
|
|
Ndead = 0,
|
|
elements = {},
|
|
casualties = {},
|
|
threatlevel0 = 0
|
|
}
|
|
|
|
|
|
--- Type.
|
|
-- @type TARGET.ObjectType
|
|
-- @field #string GROUP Target is a GROUP object.
|
|
-- @field #string UNIT Target is a UNIT object.
|
|
-- @field #string STATIC Target is a STATIC object.
|
|
-- @field #string SCENERY Target is a SCENERY object.
|
|
-- @field #string COORDINATE Target is a COORDINATE.
|
|
-- @field #string AIRBASE Target is an AIRBASE.
|
|
-- @field #string ZONE Target is a ZONE object.
|
|
TARGET.ObjectType={
|
|
GROUP="Group",
|
|
UNIT="Unit",
|
|
STATIC="Static",
|
|
SCENERY="Scenery",
|
|
COORDINATE="Coordinate",
|
|
AIRBASE="Airbase",
|
|
ZONE="Zone",
|
|
}
|
|
|
|
|
|
--- Category.
|
|
-- @type TARGET.Category
|
|
-- @field #string AIRCRAFT
|
|
-- @field #string GROUND
|
|
-- @field #string NAVAL
|
|
-- @field #string AIRBASE
|
|
-- @field #string COORDINATE
|
|
-- @field #string ZONE
|
|
TARGET.Category={
|
|
AIRCRAFT="Aircraft",
|
|
GROUND="Ground",
|
|
NAVAL="Naval",
|
|
AIRBASE="Airbase",
|
|
COORDINATE="Coordinate",
|
|
ZONE="Zone",
|
|
}
|
|
|
|
--- Object status.
|
|
-- @type TARGET.ObjectStatus
|
|
-- @field #string ALIVE Object is alive.
|
|
-- @field #string DEAD Object is dead.
|
|
TARGET.ObjectStatus={
|
|
ALIVE="Alive",
|
|
DEAD="Dead",
|
|
}
|
|
--- Target object.
|
|
-- @type TARGET.Object
|
|
-- @field #number ID Target unique ID.
|
|
-- @field #string Name Target name.
|
|
-- @field #string Type Target type.
|
|
-- @field Wrapper.Positionable#POSITIONABLE Object The object, which can be many things, e.g. a UNIT, GROUP, STATIC, SCENERY, AIRBASE or COORDINATE object.
|
|
-- @field #number Life Life points on last status update.
|
|
-- @field #number Life0 Life points of completely healthy target.
|
|
-- @field #number N0 Number of initial elements.
|
|
-- @field #number Ndead Number of dead elements.
|
|
-- @field #number Ndestroyed Number of destroyed elements.
|
|
-- @field #string Status Status "Alive" or "Dead".
|
|
-- @field Core.Point#COORDINATE Coordinate of the target object.
|
|
|
|
--- Global target ID counter.
|
|
_TARGETID=0
|
|
|
|
--- TARGET class version.
|
|
-- @field #string version
|
|
TARGET.version="0.5.2"
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- TODO list
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
-- TODO: Add pseudo functions.
|
|
-- DONE: Initial object can be nil.
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Constructor
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Create a new TARGET object and start the FSM.
|
|
-- @param #TARGET self
|
|
-- @param #table TargetObject Target object.
|
|
-- @return #TARGET self
|
|
function TARGET:New(TargetObject)
|
|
|
|
-- Inherit everything from INTEL class.
|
|
local self=BASE:Inherit(self, FSM:New()) --#TARGET
|
|
|
|
-- Increase counter.
|
|
_TARGETID=_TARGETID+1
|
|
|
|
-- Set UID.
|
|
self.uid=_TARGETID
|
|
|
|
if TargetObject then
|
|
|
|
-- Add object.
|
|
self:AddObject(TargetObject)
|
|
|
|
end
|
|
|
|
-- Defaults.
|
|
self:SetPriority()
|
|
self:SetImportance()
|
|
|
|
-- Log ID.
|
|
self.lid=string.format("TARGET #%03d | ", _TARGETID)
|
|
|
|
-- Start state.
|
|
self:SetStartState("Stopped")
|
|
|
|
-- Add FSM transitions.
|
|
-- From State --> Event --> To State
|
|
self:AddTransition("Stopped", "Start", "Alive") -- Start FSM.
|
|
self:AddTransition("*", "Status", "*") -- Status update.
|
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
|
|
|
self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged.
|
|
self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed.
|
|
self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned).
|
|
|
|
self:AddTransition("*", "Damaged", "*") -- Target was damaged.
|
|
self:AddTransition("*", "Destroyed", "Dead") -- Target was completely destroyed.
|
|
self:AddTransition("*", "Dead", "Dead") -- Target is dead. Could be destroyed or despawned.
|
|
|
|
------------------------
|
|
--- Pseudo Functions ---
|
|
------------------------
|
|
|
|
--- Triggers the FSM event "Start". Starts the TARGET. Initializes parameters and starts event handlers.
|
|
-- @function [parent=#TARGET] Start
|
|
-- @param #TARGET self
|
|
|
|
--- Triggers the FSM event "Start" after a delay. Starts the TARGET. Initializes parameters and starts event handlers.
|
|
-- @function [parent=#TARGET] __Start
|
|
-- @param #TARGET self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Stop". Stops the TARGET and all its event handlers.
|
|
-- @param #TARGET self
|
|
|
|
--- Triggers the FSM event "Stop" after a delay. Stops the TARGET and all its event handlers.
|
|
-- @function [parent=#TARGET] __Stop
|
|
-- @param #TARGET self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Status".
|
|
-- @function [parent=#TARGET] Status
|
|
-- @param #TARGET self
|
|
|
|
--- Triggers the FSM event "Status" after a delay.
|
|
-- @function [parent=#TARGET] __Status
|
|
-- @param #TARGET self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
|
|
-- Start.
|
|
self:__Start(-1)
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- User functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Create target data from a given object. Valid objects are:
|
|
--
|
|
-- * GROUP
|
|
-- * UNIT
|
|
-- * STATIC
|
|
-- * AIRBASE
|
|
-- * COORDINATE
|
|
-- * ZONE
|
|
-- * SET_GROUP
|
|
-- * SET_UNIT
|
|
-- * SET_STATIC
|
|
-- * SET_OPSGROUP
|
|
--
|
|
-- @param #TARGET self
|
|
-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE.
|
|
function TARGET:AddObject(Object)
|
|
|
|
if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_OPSGROUP") then
|
|
|
|
---
|
|
-- Sets
|
|
---
|
|
|
|
local set=Object --Core.Set#SET_GROUP
|
|
|
|
for _,object in pairs(set.Set) do
|
|
self:AddObject(object)
|
|
end
|
|
|
|
elseif Object:IsInstanceOf("SET_ZONE") then
|
|
|
|
local set=Object --Core.Set#SET_ZONE
|
|
|
|
set:SortByName()
|
|
|
|
for index,ZoneName in pairs(set.Index) do
|
|
local zone=set.Set[ZoneName] --Core.Zone#ZONE
|
|
self:_AddObject(zone)
|
|
end
|
|
|
|
else
|
|
|
|
---
|
|
-- Groups, Units, Statics, Airbases, Coordinates
|
|
---
|
|
|
|
if Object:IsInstanceOf("OPSGROUP") then
|
|
self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object.
|
|
else
|
|
self:_AddObject(Object)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--- Set priority of the target.
|
|
-- @param #TARGET self
|
|
-- @param #number Priority Priority of the target. Default 50.
|
|
-- @return #TARGET self
|
|
function TARGET:SetPriority(Priority)
|
|
self.prio=Priority or 50
|
|
return self
|
|
end
|
|
|
|
--- Set importance of the target.
|
|
-- @param #TARGET self
|
|
-- @param #number Importance Importance of the target. Default `nil`.
|
|
-- @return #TARGET self
|
|
function TARGET:SetImportance(Importance)
|
|
self.importance=Importance
|
|
return self
|
|
end
|
|
|
|
--- Check if TARGET is alive.
|
|
-- @param #TARGET self
|
|
-- @return #boolean If true, target is alive.
|
|
function TARGET:IsAlive()
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local target=_target --Ops.Target#TARGET.Object
|
|
if target.Status==TARGET.ObjectStatus.ALIVE then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Check if TARGET is destroyed.
|
|
-- @param #TARGET self
|
|
-- @return #boolean If true, target is destroyed.
|
|
function TARGET:IsDestroyed()
|
|
return self.isDestroyed
|
|
end
|
|
|
|
|
|
--- Check if TARGET is dead.
|
|
-- @param #TARGET self
|
|
-- @return #boolean If true, target is dead.
|
|
function TARGET:IsDead()
|
|
local is=self:Is("Dead")
|
|
return is
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Start & Status
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
|
-- @param #TARGET self
|
|
-- @param Wrapper.Group#GROUP Group Flight group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function TARGET:onafterStart(From, Event, To)
|
|
|
|
-- Short info.
|
|
local text=string.format("Starting Target")
|
|
self:T(self.lid..text)
|
|
|
|
self:HandleEvent(EVENTS.Dead, self.OnEventUnitDeadOrLost)
|
|
self:HandleEvent(EVENTS.UnitLost, self.OnEventUnitDeadOrLost)
|
|
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventUnitDeadOrLost)
|
|
|
|
self:__Status(-1)
|
|
end
|
|
|
|
--- On after "Status" event.
|
|
-- @param #TARGET self
|
|
-- @param Wrapper.Group#GROUP Group Flight group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function TARGET:onafterStatus(From, Event, To)
|
|
|
|
-- FSM state.
|
|
local fsmstate=self:GetState()
|
|
|
|
-- Update damage.
|
|
local damaged=false
|
|
for i,_target in pairs(self.targets) do
|
|
local target=_target --#TARGET.Object
|
|
|
|
local life=target.Life
|
|
|
|
target.Life=self:GetTargetLife(target)
|
|
|
|
if target.Life<life then
|
|
self:ObjectDamaged(target)
|
|
damaged=true
|
|
end
|
|
|
|
end
|
|
|
|
-- Target was damaged.
|
|
if damaged then
|
|
self:Damaged()
|
|
end
|
|
|
|
-- Log output verbose=1.
|
|
if self.verbose>=1 then
|
|
local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.N0, self:GetLife(), self:GetLife0(), self:GetDamage())
|
|
if damaged then
|
|
text=text.." Damaged!"
|
|
end
|
|
self:I(self.lid..text)
|
|
end
|
|
|
|
-- Log output verbose=2.
|
|
if self.verbose>=2 then
|
|
local text="Target:"
|
|
for i,_target in pairs(self.targets) do
|
|
local target=_target --#TARGET.Object
|
|
local damage=(1-target.Life/target.Life0)*100
|
|
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage)
|
|
end
|
|
self:I(self.lid..text)
|
|
end
|
|
|
|
-- Update status again in 30 sec.
|
|
if self:IsAlive() then
|
|
self:__Status(-30)
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- FSM Events
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- On after "ObjectDamaged" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #TARGET.Object Target Target object.
|
|
function TARGET:onafterObjectDamaged(From, Event, To, Target)
|
|
|
|
-- Debug info.
|
|
self:T(self.lid..string.format("Object %s damaged", Target.Name))
|
|
|
|
end
|
|
|
|
--- On after "ObjectDestroyed" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #TARGET.Object Target Target object.
|
|
function TARGET:onafterObjectDestroyed(From, Event, To, Target)
|
|
|
|
-- Debug message.
|
|
self:T(self.lid..string.format("Object %s destroyed", Target.Name))
|
|
|
|
-- Increase destroyed counter.
|
|
self.Ndestroyed=self.Ndestroyed+1
|
|
|
|
-- Call object dead event.
|
|
self:ObjectDead(Target)
|
|
|
|
end
|
|
|
|
--- On after "ObjectDead" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #TARGET.Object Target Target object.
|
|
function TARGET:onafterObjectDead(From, Event, To, Target)
|
|
|
|
-- Debug message.
|
|
self:T(self.lid..string.format("Object %s dead", Target.Name))
|
|
|
|
-- Set target status.
|
|
Target.Status=TARGET.ObjectStatus.DEAD
|
|
|
|
-- Increase dead counter.
|
|
self.Ndead=self.Ndead+1
|
|
|
|
-- Check if anyone is alive?
|
|
local dead=true
|
|
for _,_target in pairs(self.targets) do
|
|
local target=_target --#TARGET.Object
|
|
if target.Status==TARGET.ObjectStatus.ALIVE then
|
|
dead=false
|
|
end
|
|
end
|
|
|
|
-- All dead ==> Trigger destroyed event.
|
|
if dead then
|
|
|
|
if self.Ndestroyed==self.Ntargets0 then
|
|
|
|
self.isDestroyed=true
|
|
|
|
self:Destroyed()
|
|
|
|
else
|
|
|
|
self:Dead()
|
|
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--- On after "Damaged" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function TARGET:onafterDamaged(From, Event, To)
|
|
|
|
self:T(self.lid..string.format("TARGET damaged"))
|
|
|
|
end
|
|
|
|
--- On after "Destroyed" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function TARGET:onafterDestroyed(From, Event, To)
|
|
|
|
self:T(self.lid..string.format("TARGET destroyed"))
|
|
|
|
self:Dead()
|
|
|
|
end
|
|
|
|
--- On after "Dead" event.
|
|
-- @param #TARGET self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function TARGET:onafterDead(From, Event, To)
|
|
|
|
self:T(self.lid..string.format("TARGET dead"))
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Event Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Event function handling the loss of a unit.
|
|
-- @param #TARGET self
|
|
-- @param Core.Event#EVENTDATA EventData Event data.
|
|
function TARGET:OnEventUnitDeadOrLost(EventData)
|
|
|
|
local Name=EventData and EventData.IniUnitName or nil
|
|
|
|
-- Check that this is the right group.
|
|
if self:IsElement(Name) and not self:IsCasualty(Name) then
|
|
|
|
-- Debug info.
|
|
self:T3(self.lid..string.format("EVENT ID=%d: Unit %s dead or lost!", EventData.id, tostring(Name)))
|
|
|
|
-- Add to the list of casualties.
|
|
table.insert(self.casualties, Name)
|
|
|
|
-- Try to get target Group.
|
|
local target=self:GetTargetByName(EventData.IniGroupName)
|
|
|
|
-- Try unit target.
|
|
if not target then
|
|
target=self:GetTargetByName(EventData.IniUnitName)
|
|
end
|
|
|
|
-- Check if we could find a target object.
|
|
if target then
|
|
|
|
if EventData.id==EVENTS.RemoveUnit then
|
|
target.Ndead=target.Ndead+1
|
|
else
|
|
target.Ndestroyed=target.Ndestroyed+1
|
|
target.Ndead=target.Ndead+1
|
|
end
|
|
|
|
if target.Ndead==target.N0 then
|
|
|
|
if target.Ndestroyed>=target.N0 then
|
|
|
|
-- Debug message.
|
|
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
|
|
|
|
-- Trigger object destroyed event.
|
|
self:ObjectDestroyed(target)
|
|
|
|
else
|
|
|
|
-- Debug message.
|
|
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead", EventData.id, tostring(target.Name)))
|
|
|
|
-- Trigger object dead event.
|
|
self:ObjectDead(target)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end -- Event belongs to this TARGET
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Adding and Removing Targets
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Create target data from a given object.
|
|
-- @param #TARGET self
|
|
-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE.
|
|
function TARGET:_AddObject(Object)
|
|
|
|
local target={} --#TARGET.Object
|
|
|
|
target.N0=0
|
|
target.Ndead=0
|
|
target.Ndestroyed=0
|
|
|
|
if Object:IsInstanceOf("GROUP") then
|
|
|
|
local group=Object --Wrapper.Group#GROUP
|
|
|
|
target.Type=TARGET.ObjectType.GROUP
|
|
target.Name=group:GetName()
|
|
|
|
target.Coordinate=group:GetCoordinate()
|
|
|
|
local units=group:GetUnits()
|
|
|
|
target.Life=0 ; target.Life0=0
|
|
for _,_unit in pairs(units or {}) do
|
|
local unit=_unit --Wrapper.Unit#UNIT
|
|
|
|
local life=unit:GetLife()
|
|
|
|
target.Life=target.Life+life
|
|
target.Life0=target.Life0+math.max(unit:GetLife0(), life) -- There was an issue with ships that life is greater life0, which cannot be!
|
|
|
|
self.threatlevel0=self.threatlevel0+unit:GetThreatLevel()
|
|
|
|
table.insert(self.elements, unit:GetName())
|
|
|
|
target.N0=target.N0+1
|
|
end
|
|
|
|
elseif Object:IsInstanceOf("UNIT") then
|
|
|
|
local unit=Object --Wrapper.Unit#UNIT
|
|
|
|
target.Type=TARGET.ObjectType.UNIT
|
|
target.Name=unit:GetName()
|
|
|
|
target.Coordinate=unit:GetCoordinate()
|
|
|
|
if unit then
|
|
target.Life=unit:GetLife()
|
|
target.Life0=math.max(unit:GetLife0(), target.Life) -- There was an issue with ships that life is greater life0!
|
|
|
|
self.threatlevel0=self.threatlevel0+unit:GetThreatLevel()
|
|
|
|
table.insert(self.elements, unit:GetName())
|
|
|
|
target.N0=target.N0+1
|
|
end
|
|
|
|
elseif Object:IsInstanceOf("STATIC") then
|
|
|
|
local static=Object --Wrapper.Static#STATIC
|
|
|
|
target.Type=TARGET.ObjectType.STATIC
|
|
target.Name=static:GetName()
|
|
|
|
target.Coordinate=static:GetCoordinate()
|
|
|
|
if static and static:IsAlive() then
|
|
|
|
target.Life0=1
|
|
target.Life=1
|
|
target.N0=target.N0+1
|
|
|
|
table.insert(self.elements, target.Name)
|
|
|
|
end
|
|
|
|
elseif Object:IsInstanceOf("SCENERY") then
|
|
|
|
local scenery=Object --Wrapper.Scenery#SCENERY
|
|
|
|
target.Type=TARGET.ObjectType.SCENERY
|
|
target.Name=scenery:GetName()
|
|
|
|
target.Coordinate=scenery:GetCoordinate()
|
|
|
|
target.Life0=1
|
|
target.Life=1
|
|
|
|
target.N0=target.N0+1
|
|
|
|
table.insert(self.elements, target.Name)
|
|
|
|
elseif Object:IsInstanceOf("AIRBASE") then
|
|
|
|
local airbase=Object --Wrapper.Airbase#AIRBASE
|
|
|
|
target.Type=TARGET.ObjectType.AIRBASE
|
|
target.Name=airbase:GetName()
|
|
|
|
target.Coordinate=airbase:GetCoordinate()
|
|
|
|
target.Life0=1
|
|
target.Life=1
|
|
|
|
target.N0=target.N0+1
|
|
|
|
table.insert(self.elements, target.Name)
|
|
|
|
elseif Object:IsInstanceOf("COORDINATE") then
|
|
|
|
local coord=UTILS.DeepCopy(Object) --Core.Point#COORDINATE
|
|
|
|
target.Type=TARGET.ObjectType.COORDINATE
|
|
target.Name=coord:ToStringMGRS()
|
|
|
|
target.Coordinate=coord
|
|
|
|
target.Life0=1
|
|
target.Life=1
|
|
|
|
elseif Object:IsInstanceOf("ZONE_BASE") then
|
|
|
|
local zone=Object --Core.Zone#ZONE_BASE
|
|
Object=zone --:GetCoordinate()
|
|
|
|
target.Type=TARGET.ObjectType.ZONE
|
|
target.Name=zone:GetName()
|
|
|
|
target.Coordinate=zone:GetCoordinate()
|
|
|
|
target.Life0=1
|
|
target.Life=1
|
|
|
|
else
|
|
self:E(self.lid.."ERROR: Unknown object type!")
|
|
return nil
|
|
end
|
|
|
|
self.life=self.life+target.Life
|
|
self.life0=self.life0+target.Life0
|
|
|
|
self.N0=self.N0+target.N0
|
|
self.Ntargets0=self.Ntargets0+1
|
|
|
|
-- Increase counter.
|
|
self.targetcounter=self.targetcounter+1
|
|
|
|
target.ID=self.targetcounter
|
|
target.Status=TARGET.ObjectStatus.ALIVE
|
|
target.Object=Object
|
|
|
|
table.insert(self.targets, target)
|
|
|
|
if self.name==nil then
|
|
self.name=self:GetTargetName(target)
|
|
end
|
|
if self.category==nil then
|
|
self.category=self:GetTargetCategory(target)
|
|
end
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Life and Damage Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Get target life points.
|
|
-- @param #TARGET self
|
|
-- @return #number Number of initial life points when mission was planned.
|
|
function TARGET:GetLife0()
|
|
return self.life0
|
|
end
|
|
|
|
--- Get current damage.
|
|
-- @param #TARGET self
|
|
-- @return #number Damage in percent.
|
|
function TARGET:GetDamage()
|
|
local life=self:GetLife()/self:GetLife0()
|
|
local damage=1-life
|
|
return damage*100
|
|
end
|
|
|
|
--- Get target life points.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return #number Life points of target.
|
|
function TARGET:GetTargetLife(Target)
|
|
|
|
if Target.Type==TARGET.ObjectType.GROUP then
|
|
|
|
if Target.Object and Target.Object:IsAlive() then
|
|
|
|
local units=Target.Object:GetUnits()
|
|
|
|
local life=0
|
|
for _,_unit in pairs(units or {}) do
|
|
local unit=_unit --Wrapper.Unit#UNIT
|
|
life=life+unit:GetLife()
|
|
end
|
|
|
|
return life
|
|
else
|
|
return 0
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.UNIT then
|
|
|
|
local unit=Target.Object --Wrapper.Unit#UNIT
|
|
|
|
if unit and unit:IsAlive() then
|
|
|
|
-- Note! According to the profiler, there is a big difference if we "return unit:GetLife()" or "local life=unit:GetLife(); return life"!
|
|
local life=unit:GetLife()
|
|
return life
|
|
else
|
|
return 0
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.STATIC then
|
|
|
|
if Target.Object and Target.Object:IsAlive() then
|
|
return 1
|
|
else
|
|
return 0
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.SCENERY then
|
|
|
|
if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
return 1
|
|
else
|
|
return 0
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.AIRBASE then
|
|
|
|
if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
return 1
|
|
else
|
|
return 0
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
return 1
|
|
|
|
elseif Target.Type==TARGET.ObjectType.ZONE then
|
|
|
|
return 1
|
|
|
|
else
|
|
self:E("ERROR: unknown target object type in GetTargetLife!")
|
|
end
|
|
|
|
end
|
|
|
|
--- Get current life points.
|
|
-- @param #TARGET self
|
|
-- @return #number Life points of target.
|
|
function TARGET:GetLife()
|
|
|
|
local N=0
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local Target=_target --#TARGET.Object
|
|
|
|
N=N+self:GetTargetLife(Target)
|
|
|
|
end
|
|
|
|
return N
|
|
end
|
|
|
|
--- Get target 2D position vector.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return DCS#Vec2 Vector with x,y components.
|
|
function TARGET:GetTargetVec2(Target)
|
|
|
|
local vec3=self:GetTargetVec3(Target)
|
|
|
|
if vec3 then
|
|
return {x=vec3.x, y=vec3.z}
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get target 3D position vector.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return DCS#Vec3 Vector with x,y,z components.
|
|
function TARGET:GetTargetVec3(Target)
|
|
|
|
if Target.Type==TARGET.ObjectType.GROUP then
|
|
|
|
local object=Target.Object --Wrapper.Group#GROUP
|
|
|
|
if object and object:IsAlive() then
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.UNIT then
|
|
|
|
local object=Target.Object --Wrapper.Unit#UNIT
|
|
|
|
if object and object:IsAlive() then
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
else
|
|
return nil
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.STATIC then
|
|
|
|
local object=Target.Object --Wrapper.Static#STATIC
|
|
|
|
if object and object:IsAlive() then
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
else
|
|
return nil
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.SCENERY then
|
|
|
|
local object=Target.Object --Wrapper.Scenery#SCENERY
|
|
|
|
if object then
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
else
|
|
return nil
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.AIRBASE then
|
|
|
|
local object=Target.Object --Wrapper.Airbase#AIRBASE
|
|
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
|
|
--if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
--end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
local object=Target.Object --Core.Point#COORDINATE
|
|
|
|
local vec3={x=object.x, y=object.y, z=object.z}
|
|
return vec3
|
|
|
|
elseif Target.Type==TARGET.ObjectType.ZONE then
|
|
|
|
local object=Target.Object --Core.Zone#ZONE
|
|
|
|
local vec3=object:GetVec3()
|
|
return vec3
|
|
|
|
end
|
|
|
|
self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3")
|
|
end
|
|
|
|
|
|
--- Get target coordinate.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return Core.Point#COORDINATE Coordinate of the target.
|
|
function TARGET:GetTargetCoordinate(Target)
|
|
|
|
if Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
-- Coordinate is the object itself.
|
|
return Target.Object
|
|
|
|
else
|
|
|
|
-- Get updated position vector.
|
|
local vec3=self:GetTargetVec3(Target)
|
|
|
|
-- Update position. This saves us to create a new COORDINATE object each time.
|
|
if vec3 then
|
|
Target.Coordinate.x=vec3.x
|
|
Target.Coordinate.y=vec3.y
|
|
Target.Coordinate.z=vec3.z
|
|
end
|
|
|
|
return Target.Coordinate
|
|
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get target name.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return #string Name of the target object.
|
|
function TARGET:GetTargetName(Target)
|
|
|
|
if Target.Type==TARGET.ObjectType.GROUP then
|
|
|
|
if Target.Object and Target.Object:IsAlive() then
|
|
|
|
return Target.Object:GetName()
|
|
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.UNIT then
|
|
|
|
if Target.Object and Target.Object:IsAlive() then
|
|
return Target.Object:GetName()
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.STATIC then
|
|
|
|
if Target.Object and Target.Object:IsAlive() then
|
|
return Target.Object:GetName()
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.AIRBASE then
|
|
|
|
if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
return Target.Object:GetName()
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
local coord=Target.Object --Core.Point#COORDINATE
|
|
|
|
return coord:ToStringMGRS()
|
|
|
|
elseif Target.Type==TARGET.ObjectType.ZONE then
|
|
|
|
local Zone=Target.Object --Core.Zone#ZONE
|
|
|
|
return Zone:GetName()
|
|
|
|
end
|
|
|
|
return "Unknown"
|
|
end
|
|
|
|
--- Get name.
|
|
-- @param #TARGET self
|
|
-- @return #string Name of the target usually the first object.
|
|
function TARGET:GetName()
|
|
return self.name or "Unknown"
|
|
end
|
|
|
|
--- Get 2D vector.
|
|
-- @param #TARGET self
|
|
-- @return DCS#Vec2 2D vector of the target.
|
|
function TARGET:GetVec2()
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local Target=_target --#TARGET.Object
|
|
|
|
local coordinate=self:GetTargetVec2(Target)
|
|
|
|
if coordinate then
|
|
return coordinate
|
|
end
|
|
|
|
end
|
|
|
|
self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s", self.name))
|
|
return nil
|
|
end
|
|
|
|
--- Get 3D vector.
|
|
-- @param #TARGET self
|
|
-- @return DCS#Vec3 3D vector of the target.
|
|
function TARGET:GetVec3()
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local Target=_target --#TARGET.Object
|
|
|
|
local coordinate=self:GetTargetVec3(Target)
|
|
|
|
if coordinate then
|
|
return coordinate
|
|
end
|
|
|
|
end
|
|
|
|
self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s", self.name))
|
|
return nil
|
|
end
|
|
|
|
--- Get coordinate.
|
|
-- @param #TARGET self
|
|
-- @return Core.Point#COORDINATE Coordinate of the target.
|
|
function TARGET:GetCoordinate()
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local Target=_target --#TARGET.Object
|
|
|
|
local coordinate=self:GetTargetCoordinate(Target)
|
|
|
|
if coordinate then
|
|
return coordinate
|
|
end
|
|
|
|
end
|
|
|
|
self:E(self.lid..string.format("ERROR: Cannot get coordinate of target %s", self.name))
|
|
return nil
|
|
end
|
|
|
|
--- Get category.
|
|
-- @param #TARGET self
|
|
-- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`.
|
|
function TARGET:GetCategory()
|
|
return self.category
|
|
end
|
|
|
|
--- Get target category.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target object.
|
|
-- @return #TARGET.Category Target category.
|
|
function TARGET:GetTargetCategory(Target)
|
|
|
|
local category=nil
|
|
|
|
if Target.Type==TARGET.ObjectType.GROUP then
|
|
|
|
if Target.Object and Target.Object:IsAlive()~=nil then
|
|
|
|
local group=Target.Object --Wrapper.Group#GROUP
|
|
|
|
local cat=group:GetCategory()
|
|
|
|
if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then
|
|
category=TARGET.Category.AIRCRAFT
|
|
elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then
|
|
category=TARGET.Category.GROUND
|
|
elseif cat==Group.Category.SHIP then
|
|
category=TARGET.Category.NAVAL
|
|
end
|
|
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.UNIT then
|
|
|
|
if Target.Object and Target.Object:IsAlive()~=nil then
|
|
local unit=Target.Object --Wrapper.Unit#UNIT
|
|
|
|
local group=unit:GetGroup()
|
|
|
|
local cat=group:GetCategory()
|
|
|
|
if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then
|
|
category=TARGET.Category.AIRCRAFT
|
|
elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then
|
|
category=TARGET.Category.GROUND
|
|
elseif cat==Group.Category.SHIP then
|
|
category=TARGET.Category.NAVAL
|
|
end
|
|
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.STATIC then
|
|
|
|
return TARGET.Category.GROUND
|
|
|
|
elseif Target.Type==TARGET.ObjectType.SCENERY then
|
|
|
|
return TARGET.Category.GROUND
|
|
|
|
elseif Target.Type==TARGET.ObjectType.AIRBASE then
|
|
|
|
return TARGET.Category.AIRBASE
|
|
|
|
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
return TARGET.Category.COORDINATE
|
|
|
|
elseif Target.Type==TARGET.ObjectType.ZONE then
|
|
|
|
return TARGET.Category.ZONE
|
|
|
|
else
|
|
self:E("ERROR: unknown target category!")
|
|
end
|
|
|
|
return category
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Misc Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Get a target object by its name.
|
|
-- @param #TARGET self
|
|
-- @param #string ObjectName Object name.
|
|
-- @return #TARGET.Object The target object table or nil.
|
|
function TARGET:GetTargetByName(ObjectName)
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local target=_target --#TARGET.Object
|
|
if ObjectName==target.Name then
|
|
return target
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
|
|
--- Get the first target objective alive.
|
|
-- @param #TARGET self
|
|
-- @return #TARGET.Object The target objective.
|
|
function TARGET:GetObjective()
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local target=_target --#TARGET.Object
|
|
if target.Status==TARGET.ObjectStatus.ALIVE then
|
|
return target
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get the first target object alive.
|
|
-- @param #TARGET self
|
|
-- @return Wrapper.Positionable#POSITIONABLE The target object or nil.
|
|
function TARGET:GetObject()
|
|
|
|
local target=self:GetObjective()
|
|
if target then
|
|
return target.Object
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Count alive objects.
|
|
-- @param #TARGET self
|
|
-- @param #TARGET.Object Target Target objective.
|
|
-- @return #number Number of alive target objects.
|
|
function TARGET:CountObjectives(Target)
|
|
|
|
local N=0
|
|
|
|
if Target.Type==TARGET.ObjectType.GROUP then
|
|
|
|
local target=Target.Object --Wrapper.Group#GROUP
|
|
|
|
local units=target:GetUnits()
|
|
|
|
for _,_unit in pairs(units or {}) do
|
|
local unit=_unit --Wrapper.Unit#UNIT
|
|
if unit and unit:IsAlive()~=nil and unit:GetLife()>1 then
|
|
N=N+1
|
|
end
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.UNIT then
|
|
|
|
local target=Target.Object --Wrapper.Unit#UNIT
|
|
|
|
if target and target:IsAlive()~=nil and target:GetLife()>1 then
|
|
N=N+1
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.STATIC then
|
|
|
|
local target=Target.Object --Wrapper.Static#STATIC
|
|
|
|
if target and target:IsAlive() then
|
|
N=N+1
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.SCENERY then
|
|
|
|
if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
N=N+1
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.AIRBASE then
|
|
|
|
if Target.Status==TARGET.ObjectStatus.ALIVE then
|
|
N=N+1
|
|
end
|
|
|
|
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
|
|
|
-- No target we can check!
|
|
|
|
elseif Target.Type==TARGET.ObjectType.ZONE then
|
|
|
|
-- No target we can check!
|
|
|
|
else
|
|
self:E(self.lid.."ERROR: Unknown target type! Cannot count targets")
|
|
end
|
|
|
|
return N
|
|
end
|
|
|
|
--- Count alive targets.
|
|
-- @param #TARGET self
|
|
-- @return #number Number of alive target objects.
|
|
function TARGET:CountTargets()
|
|
|
|
local N=0
|
|
|
|
for _,_target in pairs(self.targets) do
|
|
local Target=_target --#TARGET.Object
|
|
|
|
N=N+self:CountObjectives(Target)
|
|
|
|
end
|
|
|
|
return N
|
|
end
|
|
|
|
--- Check if something is an element of the TARGET.
|
|
-- @param #TARGET self
|
|
-- @param #string Name The name of the potential element.
|
|
-- @return #boolean If `true`, this name is part of this TARGET.
|
|
function TARGET:IsElement(Name)
|
|
|
|
if Name==nil then
|
|
return false
|
|
end
|
|
|
|
for _,name in pairs(self.elements) do
|
|
if name==Name then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Check if something is a a casualty of this TARGET.
|
|
-- @param #TARGET self
|
|
-- @param #string Name The name of the potential element.
|
|
-- @return #boolean If `true`, this name is a casualty of this TARGET.
|
|
function TARGET:IsCasualty(Name)
|
|
|
|
if Name==nil then
|
|
return false
|
|
end
|
|
|
|
for _,name in pairs(self.casualties) do
|
|
if name==Name then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|