ARTY v0.9.96

Improved marker logic.
This commit is contained in:
funkyfranky 2018-06-10 00:05:55 +02:00
parent 403f22bd2b
commit e8ff153427

View File

@ -55,7 +55,6 @@
-- @field #number Nrockets0 Initial amount of rockets of the whole group. -- @field #number Nrockets0 Initial amount of rockets of the whole group.
-- @field #number Nmissiles0 Initial amount of missiles of the whole group. -- @field #number Nmissiles0 Initial amount of missiles of the whole group.
-- @field #number Nukes0 Initial amount of tactical nukes of the whole group. Default is 0. -- @field #number Nukes0 Initial amount of tactical nukes of the whole group. Default is 0.
-- @field #number FullAmmo Full amount of all ammunition taking the number of alive units into account.
-- @field #number StatusInterval Update interval in seconds between status updates. Default 10 seconds. -- @field #number StatusInterval Update interval in seconds between status updates. Default 10 seconds.
-- @field #number WaitForShotTime Max time in seconds to wait until fist shot event occurs after target is assigned. If time is passed without shot, the target is deleted. Default is 300 seconds. -- @field #number WaitForShotTime Max time in seconds to wait until fist shot event occurs after target is assigned. If time is passed without shot, the target is deleted. Default is 300 seconds.
-- @field #table DCSdesc DCS descriptors of the ARTY group. -- @field #table DCSdesc DCS descriptors of the ARTY group.
@ -63,6 +62,8 @@
-- @field #string DisplayName Extended type name of the ARTY group. -- @field #string DisplayName Extended type name of the ARTY group.
-- @field #number IniGroupStrength Inital number of units in the ARTY group. -- @field #number IniGroupStrength Inital number of units in the ARTY group.
-- @field #boolean IsArtillery If true, ARTY group has attribute "Artillery". This is automatically derived from the DCS descriptor table. -- @field #boolean IsArtillery If true, ARTY group has attribute "Artillery". This is automatically derived from the DCS descriptor table.
-- @field #boolean ismobile If true, ARTY group can move.
-- @field #string alias Name of the ARTY group.
-- @field #number SpeedMax Maximum speed of ARTY group in km/h. This is determined from the DCS descriptor table. -- @field #number SpeedMax Maximum speed of ARTY group in km/h. This is determined from the DCS descriptor table.
-- @field #number Speed Default speed in km/h the ARTY group moves at. Maximum speed possible is 80% of maximum speed the group can do. -- @field #number Speed Default speed in km/h the ARTY group moves at. Maximum speed possible is 80% of maximum speed the group can do.
-- @field #number RearmingDistance Safe distance in meters between ARTY group and rearming group or place at which rearming is possible. Default 100 m. -- @field #number RearmingDistance Safe distance in meters between ARTY group and rearming group or place at which rearming is possible. Default 100 m.
@ -221,8 +222,6 @@
-- * @{#ARTY.WeaponType}.Auto: Automatic weapon selection by the DCS logic. This is the default setting. -- * @{#ARTY.WeaponType}.Auto: Automatic weapon selection by the DCS logic. This is the default setting.
-- * @{#ARTY.WeaponType}.Cannon: Only cannons are used during the attack. Corresponding ammo type are shells and can be defined by @{#ARTY.SetShellTypes}. -- * @{#ARTY.WeaponType}.Cannon: Only cannons are used during the attack. Corresponding ammo type are shells and can be defined by @{#ARTY.SetShellTypes}.
-- * @{#ARTY.WeaponType}.Rockets: Only unguided are used during the attack. Corresponding ammo type are rockets/nurs and can be defined by @{#ARTY.SetRocketTypes}. -- * @{#ARTY.WeaponType}.Rockets: Only unguided are used during the attack. Corresponding ammo type are rockets/nurs and can be defined by @{#ARTY.SetRocketTypes}.
-- * @{#ARTY.WeaponType}.UnguidedAny: Any unguided weapon (cannons or rockes) will be used.
-- * @{#ARTY.WeaponType}.GuidedMissile: Any guided missiles are used during the attack. Corresponding ammo type are missiles and can be defined by @{#ARTY.SetMissileTypes}.
-- * @{#ARTY.WeaponType}.CruiseMissile: Only cruise missiles are used during the attack. Corresponding ammo type are missiles and can be defined by @{#ARTY.SetMissileTypes}. -- * @{#ARTY.WeaponType}.CruiseMissile: Only cruise missiles are used during the attack. Corresponding ammo type are missiles and can be defined by @{#ARTY.SetMissileTypes}.
-- * @{#ARTY.WeaponType}.TacticalNukes: Use tactical nuclear shells. This works only with units that have shells and is described below. -- * @{#ARTY.WeaponType}.TacticalNukes: Use tactical nuclear shells. This works only with units that have shells and is described below.
-- --
@ -294,6 +293,8 @@
-- * *weapon* Type of weapon to be used. Valid parameters are *cannon*, *rocket*, *missile*, *nuke*. Default is automatic selection. -- * *weapon* Type of weapon to be used. Valid parameters are *cannon*, *rocket*, *missile*, *nuke*. Default is automatic selection.
-- * *battery* Name of the ARTY group that the target is assigned to. Note that **the name is case sensitive** and has to be given in quotation marks. Default is all ARTY groups of the right coalition. -- * *battery* Name of the ARTY group that the target is assigned to. Note that **the name is case sensitive** and has to be given in quotation marks. Default is all ARTY groups of the right coalition.
-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. -- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement.
-- * *lldms* Specify the coordinates in Lat/Long degrees, minutes and seconds format. The actual location of the marker is unimportant here. The group will engage the coordinates given in the lldms keyword.
-- Format is DD:MM:SS[N,S] DD:MM:SS[W,E]. See example below. This can be useful when coordinates in this format are obtained from elsewhere.
-- * *readonly* The marker is readonly and cannot be deleted by users. Hence, assignment cannot be cancelled by removing the marker. -- * *readonly* The marker is readonly and cannot be deleted by users. Hence, assignment cannot be cancelled by removing the marker.
-- --
-- Here are examples of valid marker texts: -- Here are examples of valid marker texts:
@ -302,6 +303,7 @@
-- arty engage, battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15 -- arty engage, battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15
-- arty engage, battery "Blue MRLS 1", key 666 -- arty engage, battery "Blue MRLS 1", key 666
-- arty engage, battery "Paladin Alpha", weapon nukes, shots 1, time 20:15 -- arty engage, battery "Paladin Alpha", weapon nukes, shots 1, time 20:15
-- arty engage, lldms 41:51:00N 41:47:58E
-- --
-- Note that the keywords and parameters are *case insensitve*. Only exception are the battery group names. These must be exactly the same as the names of the goups defined -- Note that the keywords and parameters are *case insensitve*. Only exception are the battery group names. These must be exactly the same as the names of the goups defined
-- in the mission editor. -- in the mission editor.
@ -316,6 +318,8 @@
-- * *canceltarget* Group will cancel all running firing engagements and immidiately start to move. Default is that group will wait until is current assignment is over. -- * *canceltarget* Group will cancel all running firing engagements and immidiately start to move. Default is that group will wait until is current assignment is over.
-- * *battery* Name of the ARTY group that the relocation is assigned to. -- * *battery* Name of the ARTY group that the relocation is assigned to.
-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. -- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement.
-- * *lldms* Specify the coordinates in Lat/Long degrees, minutes and seconds format. The actual location of the marker is unimportant. The group will move to the coordinates given in the lldms keyword.
-- Format is DD:MM:SS[N,S] DD:MM:SS[W,E]. See example below.
-- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker. -- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker.
-- --
-- Here are some examples: -- Here are some examples:
@ -323,18 +327,9 @@
-- arty move, time 23:45, speed 50, on road -- arty move, time 23:45, speed 50, on road
-- arty move, battery "Blue Paladin" -- arty move, battery "Blue Paladin"
-- arty move, battery "Blue MRLS", canceltarget, speed 10, on road -- arty move, battery "Blue MRLS", canceltarget, speed 10, on road
-- arty move, lldms 41:51:00N 41:47:58E
-- --
-- ### Coordinate Independent Commands -- ### Requests
--
-- There are a couple of commands, which are independent of the position where the marker is placed.
-- These commands are
-- arty move, cancelcurrent
-- which will cancel the current relocation movement. Of course, this can be combined with the *battery* keyword to address a certain battery.
-- Same goes for targets, e.g.
-- arty engage, battery "Paladin Alpha", cancelcurrent
-- which will cancel all running firing tasks.
--
-- ### General Requests
-- --
-- Marks can also be to send requests to the ARTY group. This is done by the keyword **arty request**, which can have the keywords -- Marks can also be to send requests to the ARTY group. This is done by the keyword **arty request**, which can have the keywords
-- --
@ -347,6 +342,17 @@
-- arty request, battery "Paladin Bravo", targets -- arty request, battery "Paladin Bravo", targets
-- arty request, battery "MRLS Charly", move -- arty request, battery "MRLS Charly", move
-- --
-- The actual location of the marker is irrelevant for these requests.
--
-- ### Cancel
--
-- Current actions can be cancelled by the keyword **arty cancel**. Actions that can be cancelled are current engagements, relocations and rearming assignments.
--
-- For example
-- arty cancel, target, battery "Paladin Bravo"
-- arty cancel, move
-- arty cancel, rearming, battery "MRLS Charly"
--
-- --
-- ## Fine Tuning -- ## Fine Tuning
-- --
@ -444,13 +450,13 @@ ARTY={
Nrockets0=0, Nrockets0=0,
Nmissiles0=0, Nmissiles0=0,
Nukes0=0, Nukes0=0,
FullAmmo=0,
defaultROE="weapon_hold",
StatusInterval=10, StatusInterval=10,
WaitForShotTime=300, WaitForShotTime=300,
DCSdesc=nil, DCSdesc=nil,
Type=nil, Type=nil,
DisplayName=nil, DisplayName=nil,
alias=nil,
ismobile=true,
IniGroupStrength=0, IniGroupStrength=0,
IsArtillery=nil, IsArtillery=nil,
RearmingDistance=100, RearmingDistance=100,
@ -490,10 +496,10 @@ ARTY.WeaponType={
Auto=1073741822, Auto=1073741822,
Cannon=805306368, Cannon=805306368,
Rockets=30720, Rockets=30720,
UnguidedAny=805339120, --UnguidedAny=805339120,
GuidedMissile=268402688, --GuidedMissile=268402688,
CruiseMissile=2097152, CruiseMissile=2097152,
AntiShipMissile=65536, --AntiShipMissile=65536,
TacticalNukes=666, TacticalNukes=666,
} }
@ -563,7 +569,7 @@ ARTY.id="ARTY | "
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="0.9.95" ARTY.version="0.9.96"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -596,9 +602,9 @@ ARTY.version="0.9.95"
--- Creates a new ARTY object. --- Creates a new ARTY object.
-- @param #ARTY self -- @param #ARTY self
-- @param Wrapper.Group#GROUP group The GROUP object for which artillery tasks should be assigned. -- @param Wrapper.Group#GROUP group The GROUP object for which artillery tasks should be assigned.
-- @return #ARTY ARTY object. -- @param alias (Optional) Alias name the group will be calling itself when sending messages.
-- @return nil If group does not exist or is not a ground or naval group. -- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group.
function ARTY:New(group) function ARTY:New(group, alias)
BASE:F2(group) BASE:F2(group)
-- Inherits from FSM_CONTROLLABLE -- Inherits from FSM_CONTROLLABLE
@ -621,6 +627,12 @@ function ARTY:New(group)
-- Set the controllable for the FSM. -- Set the controllable for the FSM.
self:SetControllable(group) self:SetControllable(group)
if alias~=nil then
self.alias=tostring(alias)
else
self.alias=group:GetName()
end
-- Set the initial coordinates of the ARTY group. -- Set the initial coordinates of the ARTY group.
self.InitialCoord=group:GetCoordinate() self.InitialCoord=group:GetCoordinate()
@ -629,8 +641,6 @@ function ARTY:New(group)
local DCSunit=DCSgroup:getUnit(1) local DCSunit=DCSgroup:getUnit(1)
self.DCSdesc=DCSunit:getDesc() self.DCSdesc=DCSunit:getDesc()
--self.DCSdesc=group:GetDesc()
-- DCS descriptors. -- DCS descriptors.
self:T3(ARTY.id.."DCS descriptors for group "..group:GetName()) self:T3(ARTY.id.."DCS descriptors for group "..group:GetName())
for id,desc in pairs(self.DCSdesc) do for id,desc in pairs(self.DCSdesc) do
@ -640,6 +650,13 @@ function ARTY:New(group)
-- Maximum speed in km/h. -- Maximum speed in km/h.
self.SpeedMax=group:GetSpeedMax() self.SpeedMax=group:GetSpeedMax()
-- Group is mobile or not (e.g. mortars).
if self.SpeedMax>1 then
self.ismobile=true
else
self.ismobile=false
end
-- Set speed to 0.7 of maximum. -- Set speed to 0.7 of maximum.
self.Speed=self.SpeedMax * 0.7 self.Speed=self.SpeedMax * 0.7
@ -791,6 +808,12 @@ end
function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique)
self:F({coord=coord, time=time, speed=speed, onroad=onroad, cancel=cancel, name=name, unique=unique}) self:F({coord=coord, time=time, speed=speed, onroad=onroad, cancel=cancel, name=name, unique=unique})
-- Reject move if the group is immobile.
if not self.ismobile then
self:T(ARTY.id..string.format("%s: group is immobile. Rejecting move request!", self.Controllable:GetName()))
return nil
end
-- Default -- Default
if unique==nil then if unique==nil then
unique=false unique=false
@ -1165,6 +1188,13 @@ function ARTY:onafterStart(Controllable, From, Event, To)
end end
end end
-- Some mobility consitency checks if group cannot move.
if not self.ismobile then
self.RearmingPlaceCoord=nil
self.relocateafterfire=false
self.autorelocate=false
end
local text=string.format("\n******************************************************\n") local text=string.format("\n******************************************************\n")
text=text..string.format("Arty group = %s\n", Controllable:GetName()) text=text..string.format("Arty group = %s\n", Controllable:GetName())
text=text..string.format("Artillery attribute = %s\n", tostring(self.IsArtillery)) text=text..string.format("Artillery attribute = %s\n", tostring(self.IsArtillery))
@ -1173,6 +1203,7 @@ function ARTY:onafterStart(Controllable, From, Event, To)
text=text..string.format("Number of units = %d\n", self.IniGroupStrength) text=text..string.format("Number of units = %d\n", self.IniGroupStrength)
text=text..string.format("Speed max = %d km/h\n", self.SpeedMax) text=text..string.format("Speed max = %d km/h\n", self.SpeedMax)
text=text..string.format("Speed default = %d km/h\n", self.Speed) text=text..string.format("Speed default = %d km/h\n", self.Speed)
text=text..string.format("Is mobile = %s\n", tostring(self.ismobile))
text=text..string.format("Min range = %.1f km\n", self.minrange/1000) text=text..string.format("Min range = %.1f km\n", self.minrange/1000)
text=text..string.format("Max range = %.1f km\n", self.maxrange/1000) text=text..string.format("Max range = %.1f km\n", self.maxrange/1000)
text=text..string.format("Total ammo count = %d\n", self.Nammo0) text=text..string.format("Total ammo count = %d\n", self.Nammo0)
@ -1358,7 +1389,7 @@ function ARTY:_OnEventShot(EventData)
self.Nshots=self.Nshots+1 self.Nshots=self.Nshots+1
-- Debug output. -- Debug output.
local text=string.format("%s, fired shot %d of %d with weapon %s on target %s.", self.Controllable:GetName(), self.Nshots, self.currentTarget.nshells, _weaponName, self.currentTarget.name) local text=string.format("%s, fired shot %d of %d with weapon %s on target %s.", self.alias, self.Nshots, self.currentTarget.nshells, _weaponName, self.currentTarget.name)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug) MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug)
@ -1427,7 +1458,7 @@ function ARTY:_OnEventShot(EventData)
self:T(ARTY.id..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.Controllable:GetName(), _nammo, _nshells, _nrockets, _nmissiles)) self:T(ARTY.id..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.Controllable:GetName(), _nammo, _nshells, _nrockets, _nmissiles))
self:T(ARTY.id..string.format("Group %s uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype)) self:T(ARTY.id..string.format("Group %s uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype))
-- Default switches for cease fire and relocation.
local _ceasefire=false local _ceasefire=false
local _relocate=false local _relocate=false
@ -1474,66 +1505,6 @@ function ARTY:_OnEventShot(EventData)
end end
end end
--- Check if group is (partly) out of ammo of a special weapon type.
-- @param #ARTY self
-- @param #table targets Table of targets.
-- @return @boolean True if any target requests a weapon type that is empty.
function ARTY:_CheckOutOfAmmo(targets)
-- Get current ammo.
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
-- Special weapon type requested ==> Check if corresponding ammo is empty.
local _partlyoutofammo=false
for _,Target in pairs(targets) do
if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then
self:T(ARTY.id..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then
self:T(ARTY.id..string.format("Group %s, cannons requested for target %s but shells empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then
self:T(ARTY.id..string.format("Group %s, tactical nukes requested for target %s but nukes empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then
self:T(ARTY.id..string.format("Group %s, rockets requested for target %s but rockets empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.UnguidedAny and _nshells+_nrockets==0 then
self:T(ARTY.id..string.format("Group %s, unguided weapon requested for target %s but shells AND rockets empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.GuidedMissile and _nmissiles==0 then
self:T(ARTY.id..string.format("Group %s, guided missiles requested for target %s but all missiles empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then
self:T(ARTY.id..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.AntiShipMissile and _nmissiles==0 then
self:T(ARTY.id..string.format("Group %s, anti-ship missiles requested for target %s but all missiles empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
end
end
return _partlyoutofammo
end
--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. --- After "Start" event. Initialized ROE and alarm state. Starts the event handler.
-- @param #ARTY self -- @param #ARTY self
@ -1670,7 +1641,7 @@ function ARTY:_OnEventMarkChange(Event)
local _assign=self:_Markertext(Event.text) local _assign=self:_Markertext(Event.text)
-- Check if ENGAGE or MOVE or REQUEST keywords were found. -- Check if ENGAGE or MOVE or REQUEST keywords were found.
if _assign==nil or not (_assign.engage or _assign.move or _assign.request) then if _assign==nil or not (_assign.engage or _assign.move or _assign.request or _assign.cancel) then
return return
end end
@ -1716,13 +1687,20 @@ function ARTY:_OnEventMarkChange(Event)
end end
-- Cancel current target and return. -- Cancel current target and return.
if _assign.cancelcurrent and _validkey then if _assign.cancel and _validkey then
if _assign.move and self.currentMove then if _assign.cancelmove and self.currentMove then
self.Controllable:ClearTasks() self.Controllable:ClearTasks()
self:Arrived() self:Arrived()
end elseif _assign.canceltarget and self.currentTarget then
if _assign.engage and self.currentTarget then self.currentTarget.engaged=self.currentTarget.engaged+1
self:CeaseFire(self.currentTarget) self:CeaseFire(self.currentTarget)
elseif _assign.cancelrearm and self.is("Rearming") then
local nammo=self:GetAmmo()
if nammo>0 then
self:Rearmed()
else
self:Winchester()
end
end end
return return
end end
@ -1761,7 +1739,7 @@ function ARTY:_OnEventMarkChange(Event)
MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug)
-- Assign a relocation of the arty group. -- Assign a relocation of the arty group.
local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.canceltarget,_name, true) local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.movecanceltarget,_name, true)
if _movename~=nil then if _movename~=nil then
local _mid=self:_GetMoveIndexByName(_movename) local _mid=self:_GetMoveIndexByName(_movename)
@ -2087,18 +2065,9 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
elseif target.weapontype==ARTY.WeaponType.Rockets then elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets nfire=Nrockets
_type="rockets" _type="rockets"
elseif target.weapontype==ARTY.WeaponType.UnguidedAny then
nfire=Nshells+Nrockets
_type="shells or rockets"
elseif target.weapontype==ARTY.WeaponType.GuidedMissile then
nfire=Nmissiles
_type="guided missiles"
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles nfire=Nmissiles
_type="cruise missiles" _type="cruise missiles"
elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then
nfire=Nmissiles
_type="anti-ship missiles"
end end
-- Adjust if less than requested ammo is left. -- Adjust if less than requested ammo is left.
@ -2165,6 +2134,8 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target)
-- Clear tasks. -- Clear tasks.
self.Controllable:ClearTasks() self.Controllable:ClearTasks()
else
self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.Controllable:GetName())
end end
-- Set number of shots to zero. -- Set number of shots to zero.
@ -2365,20 +2336,21 @@ function ARTY:_CheckRearmed()
end end
-- Full Ammo count. -- Full Ammo count.
self.FullAmmo=self.Nammo0 * nunits / self.IniGroupStrength local FullAmmo=self.Nammo0 * nunits / self.IniGroupStrength
-- Rearming status in per cent. -- Rearming status in per cent.
local _rearmpc=nammo/self.FullAmmo*100 local _rearmpc=nammo/FullAmmo*100
-- Send message if rearming > 1% complete -- Send message if rearming > 1% complete
if _rearmpc>1 then if _rearmpc>1 then
local text=string.format("%s, rearming %d %% complete. nammo=%d , fullammo=%d", self.Controllable:GetName(), _rearmpc, nammo, self.FullAmmo) local text=string.format("%s, rearming %d %% complete.", self.alias, _rearmpc)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
end end
-- Return if ammo is full. Strangely, I got the case that a Paladin got one more shell than it can max carry, i.e. 40 not 39 when rearming when it still had some ammo left. -- Return if ammo is full.
if nammo>=self.FullAmmo then -- TODO: Strangely, I got the case that a Paladin got one more shell than it can max carry, i.e. 40 not 39 when rearming when it still had some ammo left. Need to report.
if nammo>=FullAmmo then
return true return true
else else
return false return false
@ -2402,7 +2374,7 @@ function ARTY:onbeforeMove(Controllable, From, Event, To, move)
self:_EventFromTo("onbeforeMove", Event, From, To) self:_EventFromTo("onbeforeMove", Event, From, To)
-- Check if group can actually move... -- Check if group can actually move...
if self.SpeedMax<1 then if not self.ismobile then
return false return false
end end
@ -2529,9 +2501,6 @@ function ARTY:onafterDead(Controllable, From, Event, To)
nunits=#units nunits=#units
end end
-- Adjust full ammo count
self.FullAmmo=self.Nammo0*nunits/self.IniGroupStrength
-- Message. -- Message.
local text=string.format("%s, one of our units just died! %d units left.", self.Controllable:GetName(), nunits) local text=string.format("%s, one of our units just died! %d units left.", self.Controllable:GetName(), nunits)
MESSAGE:New(text, 5):ToAllIf(self.Debug) MESSAGE:New(text, 5):ToAllIf(self.Debug)
@ -2785,10 +2754,10 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad)
path[#path+1]=ToCoord:WaypointGround(Speed, formation) path[#path+1]=ToCoord:WaypointGround(Speed, formation)
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true)
if self.Debug then --if self.Debug then
cpini:SmokeBlue() -- cpini:SmokeBlue()
ToCoord:SmokeBlue() -- ToCoord:SmokeBlue()
end --end
-- Init waypoints of the group. -- Init waypoints of the group.
local Waypoints={} local Waypoints={}
@ -2927,6 +2896,9 @@ function ARTY:GetAmmo(display)
-- Typename of current weapon -- Typename of current weapon
local Tammo=ammotable[w]["desc"]["typeName"] local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString = self:_split(Tammo,"%.")
local _weaponName = _weaponString[#_weaponString]
-- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3
local Category=ammotable[w].desc.category local Category=ammotable[w].desc.category
@ -2987,7 +2959,7 @@ function ARTY:GetAmmo(display)
nshells=nshells+Nammo nshells=nshells+Nammo
-- Debug info. -- Debug info.
text=text..string.format("- %d shells of type %s\n", Nammo, Tammo) text=text..string.format("- %d shells of type %s\n", Nammo, _weaponName)
elseif _gotrocket then elseif _gotrocket then
@ -2995,7 +2967,7 @@ function ARTY:GetAmmo(display)
nrockets=nrockets+Nammo nrockets=nrockets+Nammo
-- Debug info. -- Debug info.
text=text..string.format("- %d rockets of type %s\n", Nammo, Tammo) text=text..string.format("- %d rockets of type %s\n", Nammo, _weaponName)
elseif _gotmissile then elseif _gotmissile then
@ -3005,7 +2977,7 @@ function ARTY:GetAmmo(display)
end end
-- Debug info. -- Debug info.
text=text..string.format("- %d %s missiles of type %s\n", Nammo, self:_MissileCategoryName(MissileCategory), Tammo) text=text..string.format("- %d %s missiles of type %s\n", Nammo, self:_MissileCategoryName(MissileCategory), _weaponName)
else else
@ -3130,9 +3102,12 @@ function ARTY:_Markertext(text)
assignment.move=false assignment.move=false
assignment.engage=false assignment.engage=false
assignment.request=false assignment.request=false
assignment.cancel=false
assignment.readonly=false assignment.readonly=false
assignment.movecanceltarget=false
assignment.cancelmove=false
assignment.canceltarget=false assignment.canceltarget=false
assignment.cancelcurrent=false assignment.cancelrearm=false
-- Check for correct keywords. -- Check for correct keywords.
if text:lower():find("arty engage") or text:lower():find("arty attack") then if text:lower():find("arty engage") or text:lower():find("arty attack") then
@ -3141,8 +3116,10 @@ function ARTY:_Markertext(text)
assignment.move=true assignment.move=true
elseif text:lower():find("arty request") then elseif text:lower():find("arty request") then
assignment.request=true assignment.request=true
elseif text:lower():find("arty cancel") then
assignment.cancel=true
else else
self:E(ARTY.id..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" keyword specified!') self:E(ARTY.id..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" keyword specified!')
return nil return nil
end end
@ -3150,11 +3127,12 @@ function ARTY:_Markertext(text)
local keywords=self:_split(text, ",") local keywords=self:_split(text, ",")
self:T({keywords=keywords}) self:T({keywords=keywords})
for _,key in pairs(keywords) do for _,keyphrase in pairs(keywords) do
-- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma. -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
local s=self:_split(key, " ") local str=self:_split(keyphrase, " ")
local val=s[2] local key=str[1]
local val=str[2]
-- Battery name, i.e. which ARTY group should fire. -- Battery name, i.e. which ARTY group should fire.
if key:lower():find("battery") then if key:lower():find("battery") then
@ -3177,7 +3155,7 @@ function ARTY:_Markertext(text)
elseif assignment.engage and key:lower():find("shot") then elseif assignment.engage and key:lower():find("shot") then
assignment.nshells=tonumber(s[2]) assignment.nshells=tonumber(val)
self:T(ARTY.id..string.format("Key Shot=%s.", val)) self:T(ARTY.id..string.format("Key Shot=%s.", val))
elseif assignment.engage and key:lower():find("prio") then elseif assignment.engage and key:lower():find("prio") then
@ -3202,7 +3180,7 @@ function ARTY:_Markertext(text)
elseif val:lower():find("rocket") then elseif val:lower():find("rocket") then
assignment.weapontype=ARTY.WeaponType.Rockets assignment.weapontype=ARTY.WeaponType.Rockets
elseif val:lower():find("missile") then elseif val:lower():find("missile") then
assignment.weapontype=ARTY.WeaponType.GuidedMissile assignment.weapontype=ARTY.WeaponType.CruiseMissile
elseif val:lower():find("nuke") then elseif val:lower():find("nuke") then
assignment.weapontype=ARTY.WeaponType.TacticalNukes assignment.weapontype=ARTY.WeaponType.TacticalNukes
else else
@ -3225,16 +3203,11 @@ function ARTY:_Markertext(text)
assignment.readonly=true assignment.readonly=true
self:T2(ARTY.id..string.format("Key Readonly=true.")) self:T2(ARTY.id..string.format("Key Readonly=true."))
elseif assignment.move and key:lower():find("canceltarget") then elseif assignment.move and (key:lower():find("cancel target") or key:lower():find("cancel target")) then
assignment.canceltarget=true assignment.movecanceltarget=true
self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true."))
elseif (assignment.engage or assignment.move) and key:lower():find("cancelcurrent") then
assignment.cancelcurrent=true
self:T2(ARTY.id..string.format("Key Cancel Current=true."))
elseif assignment.request and key:lower():find("rearm") then elseif assignment.request and key:lower():find("rearm") then
assignment.requestrearming=true assignment.requestrearming=true
@ -3260,19 +3233,34 @@ function ARTY:_Markertext(text)
assignment.requestmoves=true assignment.requestmoves=true
self:T2(ARTY.id..string.format("Key Request Moves=true.")) self:T2(ARTY.id..string.format("Key Request Moves=true."))
elseif assignment.cancel and (key:lower():find("engagement") or key:lower():find("attack") or key:lower():find("target")) then
assignment.canceltarget=true
self:T2(ARTY.id..string.format("Key Cancel Target=true."))
elseif assignment.cancel and (key:lower():find("move") or key:lower():find("relocation")) then
assignment.cancelmove=true
self:T2(ARTY.id..string.format("Key Cancel Move=true."))
elseif assignment.cancel and key:lower():find("rearm") then
assignment.cancelrearm=true
self:T2(ARTY.id..string.format("Key Cancel Rearm=true."))
elseif key:lower():find("lldms") then elseif key:lower():find("lldms") then
local _flat = "%d+:%d+:%d+%s*[N,S]" local _flat = "%d+:%d+:%d+%s*[N,S]"
local _flon = "%d+:%d+:%d+%s*[W,E]" local _flon = "%d+:%d+:%d+%s*[W,E]"
local _lat=key:match(_flat) local _lat=keyphrase:match(_flat)
local _lon=key:match(_flon) local _lon=keyphrase:match(_flon)
self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s", _lat,_lon)) self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s format=DMS", _lat,_lon))
if _lat and _lon then if _lat and _lon then
-- Convert DMS string to DD numbers format. -- Convert DMS string to DD numbers format.
local _latitude, _longitude=self:_LLDMS2DD(_lat, _lon) local _latitude, _longitude=self:_LLDMS2DD(_lat, _lon)
self:T2(ARTY.id..string.format("Key LLDMS: lat=%.3f, long=%.3f", _latitude,_longitude)) self:T2(ARTY.id..string.format("Key LLDMS: lat=%.3f, long=%.3f format=DD", _latitude,_longitude))
-- Convert LL to coordinate object. -- Convert LL to coordinate object.
if _latitude and _longitude then if _latitude and _longitude then
@ -3464,11 +3452,11 @@ function ARTY:_CheckTargetsInRange()
for i=1,#self.targets do for i=1,#self.targets do
local _target=self.targets[i] local _target=self.targets[i]
self:T(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange))) self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
-- Check if target is in range. -- Check if target is in range.
local _inrange,_toofar,_tooclose=self:_TargetInRange(_target) local _inrange,_toofar,_tooclose=self:_TargetInRange(_target)
self:T(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose))) self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose)))
-- Init default for assigning moves into range. -- Init default for assigning moves into range.
local _movetowards=false local _movetowards=false
@ -3502,7 +3490,7 @@ function ARTY:_CheckTargetsInRange()
if _inrange then if _inrange then
-- Inform coalition that target is now in range. -- Inform coalition that target is now in range.
local text=string.format("%s, target %s is now in range.", self.Controllable:GetName(), _target.name) local text=string.format("%s, target %s is now in range.", self.alias, _target.name)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
end end
@ -3528,7 +3516,7 @@ function ARTY:_CheckTargetsInRange()
local _waytogo=_dist-self.maxrange+_safetymargin local _waytogo=_dist-self.maxrange+_safetymargin
local _heading=self:_GetHeading(_from,_target.coord) local _heading=self:_GetHeading(_from,_target.coord)
_tocoord=_from:Translate(_waytogo, _heading) _tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("Relocation to within max firing range of target %s", _target.name) _name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name)
elseif _moveaway then elseif _moveaway then
@ -3536,7 +3524,7 @@ function ARTY:_CheckTargetsInRange()
local _waytogo=_dist-self.minrange+_safetymargin local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from) local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo, _heading) _tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("Relocation to within min firing range of target %s", _target.name) _name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name)
end end
@ -3553,7 +3541,7 @@ function ARTY:_CheckTargetsInRange()
-- Update value. -- Update value.
_target.inrange=_inrange _target.inrange=_inrange
self:T(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange))) self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end end
end end
@ -3750,6 +3738,52 @@ function ARTY:_GetMoveIndexByName(name)
return nil return nil
end end
--- Check if group is (partly) out of ammo of a special weapon type.
-- @param #ARTY self
-- @param #table targets Table of targets.
-- @return @boolean True if any target requests a weapon type that is empty.
function ARTY:_CheckOutOfAmmo(targets)
-- Get current ammo.
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
-- Special weapon type requested ==> Check if corresponding ammo is empty.
local _partlyoutofammo=false
for _,Target in pairs(targets) do
if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then
self:T(ARTY.id..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then
self:T(ARTY.id..string.format("Group %s, cannons requested for target %s but shells empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then
self:T(ARTY.id..string.format("Group %s, tactical nukes requested for target %s but nukes empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then
self:T(ARTY.id..string.format("Group %s, rockets requested for target %s but rockets empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then
self:T(ARTY.id..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.", self.Controllable:GetName(), Target.name))
_partlyoutofammo=true
end
end
return _partlyoutofammo
end
--- Check if a selected weapon type is available for this target, i.e. if the current amount of ammo of this weapon type is currently available. --- Check if a selected weapon type is available for this target, i.e. if the current amount of ammo of this weapon type is currently available.
-- @param #ARTY self -- @param #ARTY self
-- @param #boolean target Target array data structure. -- @param #boolean target Target array data structure.
@ -3769,14 +3803,8 @@ function ARTY:_CheckWeaponTypeAvailable(target)
nfire=self.Nukes nfire=self.Nukes
elseif target.weapontype==ARTY.WeaponType.Rockets then elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets nfire=Nrockets
elseif target.weapontype==ARTY.WeaponType.UnguidedAny then
nfire=Nshells+Nrockets
elseif target.weapontype==ARTY.WeaponType.GuidedMissile then
nfire=Nmissiles
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles nfire=Nmissiles
elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then
nfire=Nmissiles
end end
return nfire return nfire
@ -3797,14 +3825,8 @@ function ARTY:_CheckWeaponTypePossible(target)
possible=self.Nukes0>0 possible=self.Nukes0>0
elseif target.weapontype==ARTY.WeaponType.Rockets then elseif target.weapontype==ARTY.WeaponType.Rockets then
possible=self.Nrockets0>0 possible=self.Nrockets0>0
elseif target.weapontype==ARTY.WeaponType.UnguidedAny then
possible=self.Nshells0+self.Nrockets0>0
elseif target.weapontype==ARTY.WeaponType.GuidedMissile then
possible=self.Nmissiles0>0
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
possible=self.Nmissiles0>0 possible=self.Nmissiles0>0
elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then
possible=self.Nmissiles0>0
end end
return possible return possible
@ -3904,11 +3926,11 @@ function ARTY:_TargetInRange(target, message)
if _dist < self.minrange then if _dist < self.minrange then
_inrange=false _inrange=false
_tooclose=true _tooclose=true
text=string.format("%s, target is out of range. Distance of %.1f km is below min range of %.1f km.", self.Controllable:GetName(), _dist/1000, self.minrange/1000) text=string.format("%s, target is out of range. Distance of %.1f km is below min range of %.1f km.", self.alias, _dist/1000, self.minrange/1000)
elseif _dist > self.maxrange then elseif _dist > self.maxrange then
_inrange=false _inrange=false
_toofar=true _toofar=true
text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.", self.Controllable:GetName(), _dist/1000, self.maxrange/1000) text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.", self.alias, _dist/1000, self.maxrange/1000)
end end
-- Debug output. -- Debug output.
@ -3918,7 +3940,7 @@ function ARTY:_TargetInRange(target, message)
end end
-- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range. -- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range.
if self.SpeedMax<1 and _inrange==false then if not self.ismobile and _inrange==false then
self:RemoveTarget(target.name) self:RemoveTarget(target.name)
end end
@ -3938,14 +3960,8 @@ function ARTY:_WeaponTypeName(tnumber)
name="Cannons" name="Cannons"
elseif tnumber==ARTY.WeaponType.Rockets then elseif tnumber==ARTY.WeaponType.Rockets then
name="Rockets" name="Rockets"
elseif tnumber==ARTY.WeaponType.UnguidedAny then
name="Unguided Weapons" -- (Cannon or Rockets)
elseif tnumber==ARTY.WeaponType.CruiseMissile then elseif tnumber==ARTY.WeaponType.CruiseMissile then
name="Cruise Missiles" name="Cruise Missiles"
elseif tnumber==ARTY.WeaponType.GuidedMissile then
name="Guided Missiles"
elseif tnumber==ARTY.WeaponType.AntiShipMissile then
name="Anti-Ship Missiles"
elseif tnumber==ARTY.WeaponType.TacticalNukes then elseif tnumber==ARTY.WeaponType.TacticalNukes then
name="Tactical Nukes" name="Tactical Nukes"
end end
@ -4039,7 +4055,7 @@ function ARTY:_LLDMS2DD(l1,l2)
local _format = "%d+:%d+:%d+" local _format = "%d+:%d+:%d+"
local _ldms=ll:match(_format) local _ldms=ll:match(_format)
if ldms then if _ldms then
-- Split DMS to degrees, minutes and seconds. -- Split DMS to degrees, minutes and seconds.
local _dms=self:_split(_ldms, ":") local _dms=self:_split(_ldms, ":")
@ -4071,8 +4087,8 @@ function ARTY:_LLDMS2DD(l1,l2)
end end
-- Debug text. -- Debug text.
local text=string.format("\nLatitude %.3f", _latitude) local text=string.format("\nLatitude %s", tostring(_latitude))
text=text..string.format("\nLongitude %.3f", _longitude) text=text..string.format("\nLongitude %s", tostring(_longitude))
self:T2(ARTY.id..text) self:T2(ARTY.id..text)
return _latitude,_longitude return _latitude,_longitude