Merge pull request #909 from FlightControl-Master/FF/Develop

ARTY v0.9.4
This commit is contained in:
Frank 2018-05-28 16:54:00 +02:00 committed by GitHub
commit d11d4e09a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 404 additions and 49 deletions

View File

@ -958,6 +958,15 @@ do -- COORDINATE
return Path return Path
end end
--- Gets the surface type at the coordinate.
-- @param #COORDINATE self
-- @return Dcs.DCSland#SurfaceType Surface type.
function COORDINATE:GetSurfaceType()
local vec2=self:GetVec2()
local surface=land.getSurfaceType(vec2)
return surface
end
--- Creates an explosion at the point of a certain intensity. --- Creates an explosion at the point of a certain intensity.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #number ExplosionIntensity -- @param #number ExplosionIntensity

View File

@ -53,6 +53,7 @@
-- @field #number Nshells0 Initial amount of shells of the whole group. -- @field #number Nshells0 Initial amount of shells of the whole group.
-- @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.
-- @field #number FullAmmo Full amount of all ammunition taking the number of alive units into account. -- @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.
@ -78,6 +79,14 @@
-- @field #number Nshots Number of shots fired on current target. -- @field #number Nshots Number of shots fired on current target.
-- @field #number minrange Minimum firing range in kilometers. Targets closer than this distance are not engaged. Default 0.5 km. -- @field #number minrange Minimum firing range in kilometers. Targets closer than this distance are not engaged. Default 0.5 km.
-- @field #number maxrange Maximum firing range in kilometers. Targets further away than this distance are not engaged. Default 10000 km. -- @field #number maxrange Maximum firing range in kilometers. Targets further away than this distance are not engaged. Default 10000 km.
-- @field #number nukewarhead Explosion strength of tactical nuclear warhead in kg TNT. Default 75000.
-- @field #number Nukes Number of nuclear shells, the group has available. Default is same number as normal shells. Note that if normal shells are empty, firing nukes is also not possible any more.
-- @field #number nukerange Demolition range of tactical nuclear explostions.
-- @field #boolean nukefire Ignite additional fires and smoke for nuclear explosions Default true.
-- @field #number nukefires Number of nuclear fires and subexplosions.
-- @field #boolean relocateafterfire Group will relocate after each firing task. Default false.
-- @field #number relocateRmin Minimum distance in meters the group will look for places to relocate.
-- @field #number relocateRmax Maximum distance in meters the group will look for places to relocate.
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE
---# ARTY class, extends @{Core.Fsm#FSM_CONTROLLABLE} ---# ARTY class, extends @{Core.Fsm#FSM_CONTROLLABLE}
@ -339,6 +348,7 @@ ARTY={
Nshells0=0, Nshells0=0,
Nrockets0=0, Nrockets0=0,
Nmissiles0=0, Nmissiles0=0,
Nukes0=0,
FullAmmo=0, FullAmmo=0,
StatusInterval=10, StatusInterval=10,
WaitForShotTime=300, WaitForShotTime=300,
@ -365,6 +375,14 @@ ARTY={
Nshots=0, Nshots=0,
minrange=500, minrange=500,
maxrange=1000000, maxrange=1000000,
nukewarhead=75000,
Nukes=nil,
nukefire=false,
nukefires=nil,
nukerange=nil,
relocateafterfire=false,
relocateRmin=300,
relocateRmax=800,
} }
--- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag --- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag
@ -377,6 +395,7 @@ ARTY.WeaponType={
GuidedMissile=268402688, GuidedMissile=268402688,
CruiseMissile=2097152, CruiseMissile=2097152,
AntiShipMissile=65536, AntiShipMissile=65536,
TacticalNukes=666,
} }
--- Some ID to identify who we are in output of the DCS.log file. --- Some ID to identify who we are in output of the DCS.log file.
@ -385,7 +404,7 @@ ARTY.id="ARTY | "
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="0.9.1" ARTY.version="0.9.4"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -459,7 +478,6 @@ function ARTY:New(group)
-- Maximum speed in km/h. -- Maximum speed in km/h.
self.SpeedMax=group:GetSpeedMax() self.SpeedMax=group:GetSpeedMax()
--self.SpeedMax=self.DCSdesc.speedMax*3.6
-- 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
@ -601,9 +619,9 @@ end
--- Assign coordinate to where the ARTY group should move. --- Assign coordinate to where the ARTY group should move.
-- @param #ARTY self -- @param #ARTY self
-- @param Core.Point#COORDINATE coord Coordinates of the target. -- @param Core.Point#COORDINATE coord Coordinates of the new position.
-- @param #string time (Optional) Day time at which the group should start moving. Passed as a string in format "08:13:45". -- @param #string time (Optional) Day time at which the group should start moving. Passed as a string in format "08:13:45". Default is now.
-- @param #number speed (Optinal) Speed in km/h the group should move at. Default 50 km/h. -- @param #number speed (Optinal) Speed in km/h the group should move at. Default 70% of max posible speed of group.
-- @param #boolean onroad (Optional) If true, group will mainly use roads. Default off, i.e. go directly towards the specified coordinate. -- @param #boolean onroad (Optional) If true, group will mainly use roads. Default off, i.e. go directly towards the specified coordinate.
-- @param #boolean cancel (Optional) If true, cancel any running attack when move should begin. Default is false. -- @param #boolean cancel (Optional) If true, cancel any running attack when move should begin. Default is false.
-- @param #string name (Optional) Name of the coordinate. Default is LL DMS string of the coordinate. If the name was already given, the numbering "#01", "#02",... is appended automatically. -- @param #string name (Optional) Name of the coordinate. Default is LL DMS string of the coordinate. If the name was already given, the numbering "#01", "#02",... is appended automatically.
@ -620,8 +638,18 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name)
-- Default is current time if no time was specified. -- Default is current time if no time was specified.
time=time or self:_SecondsToClock(timer.getAbsTime()) time=time or self:_SecondsToClock(timer.getAbsTime())
-- Default speed is 50 km/h. -- Get max speed of group.
speed=speed or 50 local speedmax=self.Controllable:GetSpeedMax()
-- Default speed is 70% of max speed.
if speed then
speed=math.min(speed, speedmax)
elseif self.Speed then
speed=self.Speed
else
speed=speedmax*0.7
end
-- Default is off road. -- Default is off road.
if onroad==nil then if onroad==nil then
@ -639,6 +667,10 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name)
-- Prepare move array. -- Prepare move array.
local _move={name=_name, coord=coord, time=_time, speed=speed, onroad=onroad, cancel=cancel} local _move={name=_name, coord=coord, time=_time, speed=speed, onroad=onroad, cancel=cancel}
if self.Debug then
coord:MarkToAll(string.format("Battery %s move position.", self.Controllable:GetName()))
end
-- Add to table. -- Add to table.
table.insert(self.moves, _move) table.insert(self.moves, _move)
@ -822,6 +854,51 @@ function ARTY:SetMissileTypes(tableofnames)
end end
end end
--- Set number of tactical nuclear warheads available to the group.
-- Note that it can be max the number of normal shells. Also if all normal shells are empty, firing nuclear shells is also not possible any more until group gets rearmed.
-- @param #ARTY self
-- @param #number n Number of warheads for the whole group.
function ARTY:SetTacNukeShells(n)
self.Nukes=n
end
--- Set nuclear warhead explosion strength.
-- @param #ARTY self
-- @param #number strength Explosion strength in kilo tons TNT. Default is 0.075 kt.
function ARTY:SetTacNukeWarhead(strength)
self.nukewarhead=strength or 0.075
self.nukewarhead=self.nukewarhead*1000*1000 -- convert to kg TNT.
end
--- Set nuclear fires and extra demolition explosions.
-- @param #ARTY self
-- @param #number nfires (Optional) Number of big smoke and fire objects created in the demolition zone.
-- @param #number demolitionrange (Optional) Demolition range in meters.
function ARTY:SetTacNukeFires(nfires, range)
self.nukefire=true
self.nukefires=nfires
self.nukerange=range
end
--- Set relocate after firing. Group will find a new location after each engagement. Default is off
-- @param #ARTY self
-- @param #number switch (Optional) If true, activate relocation. If false, deactivate relocation.
function ARTY:SetRelocateAfterEngagement(switch)
if switch==nil then
switch=true
end
self.relocateafterfire=switch
end
--- Set relocation distance.
-- @param #ARTY self
-- @param #number rmax (Optional) Max distance in meters, the group will move to relocate. Default is 800 m.
-- @param #number rmin (Optional) Min distance in meters, the group will move to relocate. Default is 300 m.
function ARTY:SetRelocateDistance(rmax, rmin)
self.relocateRmax=rmax or 800
self.relocateRmin=rmin or 300
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Start Event -- FSM Start Event
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -843,6 +920,19 @@ function ARTY:onafterStart(Controllable, From, Event, To)
-- Get Ammo. -- Get Ammo.
self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0=self:GetAmmo(self.Debug) self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0=self:GetAmmo(self.Debug)
-- Init nuclear explosion parameters if they were not set by user.
if self.nukerange==nil then
self.nukerange=1500/75000*self.nukewarhead -- linear dependence
end
if self.nukefires==nil then
self.nukefires=20/1000/1000*self.nukerange*self.nukerange
end
if self.Nukes==nil then
self.Nukes0=self.Nshells0
else
self.Nukes0=self.Nukes
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))
@ -857,6 +947,10 @@ function ARTY:onafterStart(Controllable, From, Event, To)
text=text..string.format("Number of shells = %d\n", self.Nshells0) text=text..string.format("Number of shells = %d\n", self.Nshells0)
text=text..string.format("Number of rockets = %d\n", self.Nrockets0) text=text..string.format("Number of rockets = %d\n", self.Nrockets0)
text=text..string.format("Number of missiles = %d\n", self.Nmissiles0) text=text..string.format("Number of missiles = %d\n", self.Nmissiles0)
text=text..string.format("Number of nukes = %d\n", self.Nukes0)
text=text..string.format("Nuclear warhead = %d tons TNT\n", self.nukewarhead/1000)
text=text..string.format("Nuclear demolition = %d m\n", self.nukerange)
text=text..string.format("Nuclear fires = %d (active=%s)\n", self.nukefires, tostring(self.nukefire))
if self.RearmingGroup or self.RearmingPlaceCoord then if self.RearmingGroup or self.RearmingPlaceCoord then
text=text..string.format("Rearming safe dist. = %d m\n", self.RearmingDistance) text=text..string.format("Rearming safe dist. = %d m\n", self.RearmingDistance)
end end
@ -870,6 +964,9 @@ function ARTY:onafterStart(Controllable, From, Event, To)
text=text..string.format("Rearming coord dist = %d m\n", dist) text=text..string.format("Rearming coord dist = %d m\n", dist)
text=text..string.format("Rearming ARTY roads = %s\n", tostring(self.RearmingArtyOnRoad)) text=text..string.format("Rearming ARTY roads = %s\n", tostring(self.RearmingArtyOnRoad))
end end
text=text..string.format("Relocate after fire = %s\n", tostring(self.relocateafterfire))
text=text..string.format("Relocate min dist. = %d\n m", self.relocateRmin)
text=text..string.format("Relocate max dist. = %d\n m", self.relocateRmax)
text=text..string.format("******************************************************\n") text=text..string.format("******************************************************\n")
text=text..string.format("Targets:\n") text=text..string.format("Targets:\n")
for _, target in pairs(self.targets) do for _, target in pairs(self.targets) do
@ -928,6 +1025,7 @@ function ARTY:_StatusReport()
text=text..string.format("Number of shells = %d\n", Nshells) text=text..string.format("Number of shells = %d\n", Nshells)
text=text..string.format("Number of rockets = %d\n", Nrockets) text=text..string.format("Number of rockets = %d\n", Nrockets)
text=text..string.format("Number of missiles = %d\n", Nmissiles) text=text..string.format("Number of missiles = %d\n", Nmissiles)
text=text..string.format("Number of nukes = %d\n", self.Nukes)
if self.currentTarget then if self.currentTarget then
text=text..string.format("Current Target = %s\n", tostring(self.currentTarget.name)) text=text..string.format("Current Target = %s\n", tostring(self.currentTarget.name))
text=text..string.format("Curr. Tgt assigned = %d\n", Tnow-self.currentTarget.Tassigned) text=text..string.format("Curr. Tgt assigned = %d\n", Tnow-self.currentTarget.Tassigned)
@ -952,6 +1050,115 @@ end
-- Event Handling -- Event Handling
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Model a nuclear blast/destruction by creating fires and destroy scenery.
-- @param #ARTY self
-- @param Core.Point#COORDINATE _coord Coordinate of the impact point (center of the blast).
function ARTY:_NuclearBlast(_coord)
local S0=self.nukewarhead
local R0=self.nukerange
-- Number of fires
local N0=self.nukefires
-- Create an explosion at the last known position.
_coord:Explosion(S0)
-- Huge fire at direct impact point.
--if self.nukefire then
_coord:BigSmokeAndFireHuge()
--end
-- Create a table of fire coordinates within the demolition zone.
local _fires={}
for i=1,N0 do
local _fire=_coord:GetRandomCoordinateInRadius(R0)
local _dist=_fire:Get2DDistance(_coord)
table.insert(_fires, {distance=_dist, coord=_fire})
end
-- Sort scenery wrt to distance from impact point.
local _sort = function(a,b) return a.distance < b.distance end
table.sort(_fires,_sort)
local function _explosion(R)
-- At R=R0 ==> explosion strength is 1% of S0 at impact point.
local alpha=math.log(100)
local strength=S0*math.exp(-alpha*R/R0)
self:T2(ARTY.id..string.format("Nuclear explosion strength s(%.1f m) = %.5f (s/s0=%.1f %%), alpha=%.3f", R, strength, strength/S0*100, alpha))
return strength
end
local function ignite(_fires)
for _,fire in pairs(_fires) do
local _fire=fire.coord --Core.Point#COORDINATE
-- Get distance to impact and calc exponential explosion strength.
local R=_fire:Get2DDistance(_coord)
local S=_explosion(R)
self:T2(ARTY.id..string.format("Explosion r=%.1f, s=%.3f", R, S))
-- Get a random Big Smoke and fire object.
local _preset=math.random(0,7)
local _density=S/S0 --math.random()+0.1
_fire:BigSmokeAndFire(_preset,_density)
_fire:Explosion(S)
end
end
if self.nukefire==true then
ignite(_fires)
end
--[[
local ZoneNuke=ZONE_RADIUS:New("Nukezone", _coord:GetVec2(), 2000)
-- Scan for Scenery objects.
ZoneNuke:Scan(Object.Category.SCENERY)
-- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group.
local scenery={}
for SceneryTypeName, SceneryData in pairs(ZoneNuke:GetScannedScenery()) do
for SceneryName, SceneryObject in pairs(SceneryData) do
local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY
-- Position of the scenery object.
local spos=SceneryObject:GetCoordinate()
-- Distance from group to impact point.
local distance= spos:Get2DDistance(_coord)
-- Place markers on every possible scenery object.
if self.Debug then
local MarkerID=spos:MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(), SceneryObject:GetTypeName()))
local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS())
self:T2(SUPPRESSION.id..text)
end
-- Add to table.
table.insert(scenery, {object=SceneryObject, distance=distance})
--SceneryObject:Destroy()
end
end
-- Sort scenery wrt to distance from impact point.
-- local _sort = function(a,b) return a.distance < b.distance end
-- table.sort(scenery,_sort)
-- for _,object in pairs(scenery) do
-- local sobject=object -- Wrapper.Scenery#SCENERY
-- sobject:Destroy()
-- end
]]
end
--- Eventhandler for shot event. --- Eventhandler for shot event.
-- @param #ARTY self -- @param #ARTY self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
@ -985,61 +1192,128 @@ function ARTY:_OnEventShot(EventData)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 5):ToAllIf(self.report or self.Debug) MESSAGE:New(text, 5):ToAllIf(self.report or self.Debug)
-- Last known position of the weapon fired.
local _lastpos={x=0, y=0, z=0}
--- Track the position of the weapon if it is supposed to model a tac nuke.
-- @param #table _weapon
local function _TrackWeapon(_weapon)
-- When the pcall status returns false the weapon has hit.
local _status,_currpos = pcall(
function()
return _weapon:getPoint()
end)
self:T(ARTY.id..string.format("ARTY %s: Weapon still in air: %s", self.Controllable:GetName(), tostring(_status)))
if _status then
-- Update last position.
_lastpos={x=_currpos.x, y=_currpos.y, z=_currpos.z}
-- Check again in 0.05 seconds.
--return timer.getTime() + self.dtBombtrack
return timer.getTime() + 0.05
else
local _impactcoord=COORDINATE:NewFromVec3(_lastpos)
-- Create a "nuclear" explosion and blast at the impact point.
SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
end
end
-- Start track the shell if we want to model a tactical nuke.
if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0 then
self:T(ARTY.id..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.Controllable:GetName()))
timer.scheduleFunction(_TrackWeapon, EventData.weapon, timer.getTime() + 2.0)
end
-- Get current ammo. -- Get current ammo.
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
-- Decrease available nukes.
if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then
self.Nukes=self.Nukes-1
end
local _outofammo=false
if _nammo==0 then if _nammo==0 then
self:T(ARTY.id..string.format("Group %s completely out of ammo.", self.Controllable:GetName())) self:T(ARTY.id..string.format("Group %s completely out of ammo.", self.Controllable:GetName()))
self:CeaseFire(self.currentTarget) _outofammo=true
self:Winchester()
-- Current target is deallocated ==> return
return
end end
-- Weapon type name for current target. -- Weapon type name for current target.
local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype) local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype)
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:T2(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))
-- Special weapon type requested ==> Check if corresponding ammo is empty. -- Special weapon type requested ==> Check if corresponding ammo is empty.
local _partlyoutofammo=false
if self.currentTarget.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then if self.currentTarget.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then
self:T(ARTY.id.."Group %s, cannons requested but shells empty.", self.Controllable:GetName()) self:T(ARTY.id..string.format("Group %s, cannons requested but shells empty.", self.Controllable:GetName()))
self:CeaseFire(self.currentTarget) _partlyoutofammo=true
return
elseif self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then
self:T(ARTY.id..string.format("Group %s, tactical nukes requested but nukes empty.", self.Controllable:GetName()))
_partlyoutofammo=true
elseif self.currentTarget.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then elseif self.currentTarget.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then
self:T(ARTY.id.."Group %s, rockets requested but rockets empty.", self.Controllable:GetName()) self:T(ARTY.id..string.format("Group %s, rockets requested but rockets empty.", self.Controllable:GetName()))
self:CeaseFire(self.currentTarget) _partlyoutofammo=true
return
elseif self.currentTarget.weapontype==ARTY.WeaponType.UnguidedAny and _nshells+_nrockets==0 then elseif self.currentTarget.weapontype==ARTY.WeaponType.UnguidedAny and _nshells+_nrockets==0 then
self:T(ARTY.id.."Group %s, unguided weapon requested but shells AND rockets empty.", self.Controllable:GetName()) self:T(ARTY.id..string.format("Group %s, unguided weapon requested but shells AND rockets empty.", self.Controllable:GetName()))
self:CeaseFire(self.currentTarget) _partlyoutofammo=true
return
elseif (self.currentTarget.weapontype==ARTY.WeaponType.GuidedMissile or self.currentTarget.weapontype==ARTY.WeaponType.CruiseMissile or self.currentTarget.weapontype==ARTY.WeaponType.AntiShipMissile) and _nmissiles==0 then elseif (self.currentTarget.weapontype==ARTY.WeaponType.GuidedMissile or self.currentTarget.weapontype==ARTY.WeaponType.CruiseMissile or self.currentTarget.weapontype==ARTY.WeaponType.AntiShipMissile) and _nmissiles==0 then
self:T(ARTY.id.."Group %s, guided, anti-ship or cruise missiles requested but all missiles empty.", self.Controllable:GetName()) self:T(ARTY.id..string.format("Group %s, guided, anti-ship or cruise missiles requested but all missiles empty.", self.Controllable:GetName()))
self:CeaseFire(self.currentTarget) _partlyoutofammo=true
return
end end
-- Check if number of shots reached max. -- Check if number of shots reached max.
local _ceasefire=false
if self.Nshots >= self.currentTarget.nshells then if self.Nshots >= self.currentTarget.nshells then
local text=string.format("Group %s stop firing on target %s.", self.Controllable:GetName(), self.currentTarget.name) local text=string.format("Group %s stop firing on target %s.", self.Controllable:GetName(), self.currentTarget.name)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug) MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Cease fire. -- Cease fire.
_ceasefire=true
end
-- Check if we are (partly) out of ammo.
if _outofammo or _partlyoutofammo then
_ceasefire=true
end
-- Cease fire on current target.
if _ceasefire then
self:CeaseFire(self.currentTarget) self:CeaseFire(self.currentTarget)
end end
-- Group is out of ammo (or partly and can rearm) ==> Winchester (==> Rearm).
if _outofammo or (_partlyoutofammo and self.RearmingGroup ~=nil) then
self:Winchester()
return
end
-- Relocate position
if self.Nshots >= self.currentTarget.nshells and self.relocateafterfire then
self:_Relocate()
end
else else
self:E(ARTY.id..string.format("ERROR: No current target for group %s?!", self.Controllable:GetName())) self:E(ARTY.id..string.format("ERROR: No current target for group %s?!", self.Controllable:GetName()))
end end
@ -1217,6 +1491,35 @@ function ARTY:onbeforeOpenFire(Controllable, From, Event, To, target)
return false return false
end end
-- Get ammo.
local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo()
local nfire=Nammo
if target.weapontype==ARTY.WeaponType.Auto then
nfire=Nammo
elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Nshells
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
elseif target.weapontype==ARTY.WeaponType.Rockets then
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
nfire=Nmissiles
elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then
nfire=Nmissiles
end
-- Adjust if less than requested ammo is left.
target.nshells=math.min(target.nshells, nfire)
-- No ammo left ==> deny transition.
if target.nshells<1 then
return false
end
return true return true
end end
@ -1230,9 +1533,6 @@ end
function ARTY:onafterOpenFire(Controllable, From, Event, To, target) function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
self:_EventFromTo("onafterOpenFire", Event, From, To) self:_EventFromTo("onafterOpenFire", Event, From, To)
--local _coord=target.coord --Core.Point#COORDINATE
--_coord:MarkToAll("Arty Target")
-- Get target array index. -- Get target array index.
local id=self:_GetTargetIndexByName(target.name) local id=self:_GetTargetIndexByName(target.name)
@ -1253,37 +1553,46 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo()
local nfire=Nammo local nfire=Nammo
local _type="shots" local _type="shots"
if self.WeaponType==ARTY.WeaponType.Auto then if target.weapontype==ARTY.WeaponType.Auto then
nfire=Nammo nfire=Nammo
_type="shots" _type="shots"
elseif self.WeaponType==ARTY.WeaponType.Cannon then elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Nshells nfire=Nshells
_type="shells" _type="shells"
elseif self.WeaponType==ARTY.WeaponType.Rockets then elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
_type="nuclear shells"
elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets nfire=Nrockets
_type="rockets" _type="rockets"
elseif self.WeaponType==ARTY.WeaponType.UnguidedAny then elseif target.weapontype==ARTY.WeaponType.UnguidedAny then
nfire=Nshells+Nrockets nfire=Nshells+Nrockets
_type="shells or rockets" _type="shells or rockets"
elseif self.WeaponType==ARTY.WeaponType.GuidedMissile then elseif target.weapontype==ARTY.WeaponType.GuidedMissile then
nfire=Nmissiles nfire=Nmissiles
_type="guided missiles" _type="guided missiles"
elseif self.WeaponType==ARTY.WeaponType.CruiseMissile then elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles nfire=Nmissiles
_type="cruise missiles" _type="cruise missiles"
elseif self.WeaponType==ARTY.WeaponType.AntiShipMissile then elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then
nfire=Nmissiles nfire=Nmissiles
_type="anti-ship missiles" _type="anti-ship missiles"
end end
-- Adjust if less than requested ammo is left. -- Adjust if less than requested ammo is left.
local _n=math.min(target.nshells, nfire) target.nshells=math.min(target.nshells, nfire)
-- Send message. -- Send message.
local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.", Controllable:GetName(), target.name, _n, _type, range/1000) local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.", Controllable:GetName(), target.name, target.nshells, _type, range/1000)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report) MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report)
if self.Debug then
local _coord=target.coord --Core.Point#COORDINATE
local text=string.format("ARTY %s, Target %s, n=%d, weapon=%s", self.Controllable:GetName(), target.name, target.nshells, self:_WeaponTypeName(target.weapontype))
_coord:MarkToAll(text)
end
-- Start firing. -- Start firing.
self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype) self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype)
@ -1482,6 +1791,9 @@ function ARTY:onafterRearmed(Controllable, From, Event, To)
self:T(ARTY.id..text) self:T(ARTY.id..text)
MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug)
-- "Rearm" tactical nukes as well.
self.Nukes=self.Nukes0
-- Route ARTY group back to where it came from (if distance is > 100 m). -- Route ARTY group back to where it came from (if distance is > 100 m).
local d1=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) local d1=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord)
if d1 > self.RearmingDistance then if d1 > self.RearmingDistance then
@ -1726,6 +2038,11 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype)
-- Controllable. -- Controllable.
local group=self.Controllable --Wrapper.Group#GROUP local group=self.Controllable --Wrapper.Group#GROUP
-- Tactical nukes are actually cannon shells.
if weapontype==ARTY.WeaponType.TacticalNukes then
weapontype=ARTY.WeaponType.Cannon
end
-- Set ROE to weapon free. -- Set ROE to weapon free.
group:OptionROEOpenFire() group:OptionROEOpenFire()
@ -1739,7 +2056,33 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype)
group:SetTask(fire) group:SetTask(fire)
end end
--- Relocate to another position, e.g. after an engagement to avoid couter strikes.
-- @param #ARTY self
function ARTY:_Relocate()
-- Current position.
local _pos=self.Controllable:GetCoordinate()
local _new=nil
local _gotit=false
local _n=0
local _nmax=1000
repeat
-- Get a random coordinate.
_new=_pos:GetRandomCoordinateInRadius(self.relocateRmax, self.relocateRmin)
local _surface=_new:GetSurfaceType()
-- Check that new coordinate is not water(-ish).
if _surface~=land.SurfaceType.WATER and _surface~=land.SurfaceType.SHALLOW_WATER then
_gotit=true
end
until _gotit or _n>_nmax
-- Assign relocation
if _gotit then
self:AssignMoveCoord(_new, nil, nil, false, false)
end
end
--- Sort targets with respect to priority and number of times it was already engaged. --- Sort targets with respect to priority and number of times it was already engaged.
-- @param #ARTY self -- @param #ARTY self
@ -1865,6 +2208,7 @@ end
-- @param #ARTY self -- @param #ARTY self
-- @return #table Target which is due to be attacked now or nil if no target could be found. -- @return #table Target which is due to be attacked now or nil if no target could be found.
function ARTY:_CheckNormalTargets() function ARTY:_CheckNormalTargets()
self:F3()
-- Sort targets w.r.t. prio and number times engaged already. -- Sort targets w.r.t. prio and number times engaged already.
self:_SortTargetQueuePrio() self:_SortTargetQueuePrio()
@ -2226,7 +2570,7 @@ function ARTY:_TargetInRange(target)
end end
-- Remove target if ARTY group cannot move. No change to be ever in range. -- Remove target if ARTY group cannot move. No change to be ever in range.
if self.Speed==0 then if self.SpeedMax<1 and _inrange==false then
self:RemoveTarget(target.name) self:RemoveTarget(target.name)
end end
@ -2254,6 +2598,8 @@ function ARTY:_WeaponTypeName(tnumber)
name="Guided Missiles" name="Guided Missiles"
elseif tnumber==ARTY.WeaponType.AntiShipMissile then elseif tnumber==ARTY.WeaponType.AntiShipMissile then
name="Anti-Ship Missiles" name="Anti-Ship Missiles"
elseif tnumber==ARTY.WeaponType.TacticalNukes then
name="Tactical Nukes"
end end
return name return name
end end
@ -2439,11 +2785,11 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad)
local _last=ToCoord:GetClosestPointToRoad() local _last=ToCoord:GetClosestPointToRoad()
-- First point on road. -- First point on road.
path[#path+1]=_first:WaypointGround(Speed, "On road") path[#path+1]=_first:WaypointGround(Speed, "On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false)
-- Last point on road. -- Last point on road.
path[#path+1]=_last:WaypointGround(Speed, "On road") path[#path+1]=_last:WaypointGround(Speed, "On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false)
end end