From b6ac79a9dfa4f767c345dc9c0c8ae3203b29d29c Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 28 May 2018 22:19:35 +0200 Subject: [PATCH 01/16] ARTY Mark WIP --- Moose Development/Moose/Core/Event.lua | 9 +++++ .../Moose/Functional/Artillery.lua | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index dd739a0d9..57559d965 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -933,6 +933,15 @@ function EVENT:onEvent( Event ) --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + if Event.idx then + Event.MarkID=Event.idx + Event.MarkVec3=Event.pos + Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) + Event.MarkText=Event.text + Event.MarkCoalition=Event.coalition + Event.MarkGroupID = Event.groupID + end + if Event.cargo then Event.Cargo = Event.cargo Event.CargoName = Event.cargo.Name diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 1242e44cd..ba6300655 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -1003,11 +1003,34 @@ function ARTY:onafterStart(Controllable, From, Event, To) -- Add event handler. self:HandleEvent(EVENTS.Shot, self._OnEventShot) self:HandleEvent(EVENTS.Dead, self._OnEventDead) + self:HandleEvent(EVENTS.MarkAdded, self._OnEventMarkAdded) + + -- Add DCS event handler. + world.addEventHandler(self) -- Start checking status. self:__Status(self.StatusInterval) end +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #ARTY self +-- @param #table Event +function ART:onEvent(Event) + + if Event then + + if Event.id==world.event.S_EVENT_MARK_ADDED then + env.info("FF mark added") + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + env.info("FF mark changed") + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + env.info("FF mark removed") + end + + end + +end + --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self function ARTY:_StatusReport() @@ -1159,6 +1182,18 @@ function ARTY:_NuclearBlast(_coord) end +--- Eventhandler for shot event. +-- @param #ARTY self +-- @param Core.Event#EVENTDATA EventData +function ARTY:_OnMarkAdded(EventData) + self:F(EventData) + if EventData.MarkCoordinate then + local coord=EventData.MarkCoordinate --Core.Point#COORDINATE + + coord:SmokeGreen() + end +end + --- Eventhandler for shot event. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData @@ -2081,6 +2116,8 @@ function ARTY:_Relocate() if _surface~=land.SurfaceType.WATER and _surface~=land.SurfaceType.SHALLOW_WATER then _gotit=true end + -- Increase counter. + _n=_n+1 until _gotit or _n>_nmax -- Assign relocation. From fd4d7f49a54175ffa2676566f15a1d70043faf67 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 30 May 2018 00:39:43 +0200 Subject: [PATCH 02/16] ARTY v0.9.6 Added first WIP version with Markers. --- .../Moose/Functional/Artillery.lua | 289 ++++++++++++++++-- 1 file changed, 266 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index ba6300655..8fffe20a5 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -87,6 +87,8 @@ -- @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. +-- @field #boolean markallow If true, Players are allowed to assign targets and moves for ARTY group by placing markers on the F10 map. Default is false. +-- @field #number markkey Authorization key. Only player who know this key can assign targets and moves via markers on the F10 map. Default no authorization required. -- @extends Core.Fsm#FSM_CONTROLLABLE ---# ARTY class, extends @{Core.Fsm#FSM_CONTROLLABLE} @@ -366,11 +368,8 @@ ARTY={ RearmingArtyOnRoad=false, InitialCoord=nil, report=true, - --ammoshells={"weapons.shells"}, ammoshells={}, - --ammorockets={"weapons.nurs"}, ammorockets={}, - --ammomissiles={"weapons.missiles"}, ammomissiles={}, Nshots=0, minrange=500, @@ -383,6 +382,8 @@ ARTY={ relocateafterfire=false, relocateRmin=300, relocateRmax=800, + markallow=false, + markkey=nil, } --- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag @@ -404,7 +405,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.5" +ARTY.version="0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -625,16 +626,29 @@ end -- @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 #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 #boolean unique (Optional) Move is unique. If the move name is already known, the move is rejected. Default false. -- @return #string Name of the move. Can be used for further reference, e.g. deleting the move from the list. -function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name) - self:F({coord=coord, time=time, speed=speed, onroad=onroad, cancel=cancel, name=name}) - - -- Name of the target. - local _name=name or coord:ToStringLLDMS() +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}) - -- Check if the name has already been used for another target. If so, the function returns a new unique name. - _name=self:_CheckName(self.moves, _name) + -- Default + if unique==nil then + unique=false + end + -- Name of the target. + local _name=name or coord:ToStringLLDMS() + local _unique=true + + -- Check if the name has already been used for another target. If so, the function returns a new unique name. + _name,_unique=self:_CheckName(self.moves, _name, not unique) + + -- Move name should be unique and is not. + if unique==true and _unique==false then + self:T(ARTY.id..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!", self.Controllable:GetName(), _name)) + return nil + end + -- Default is current time if no time was specified. time=time or self:_SecondsToClock(timer.getAbsTime()) @@ -649,8 +663,7 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name) else speed=speedmax*0.7 end - - + -- Default is off road. if onroad==nil then onroad=false @@ -899,6 +912,20 @@ function ARTY:SetRelocateDistance(rmax, rmin) self.relocateRmin=rmin or 300 end +--- Enable assigning targets by placing markers on the F10 map. +-- @param #ARTY self +-- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. +function ARTY:SetMarkTargetsOn(key) + self.markkey=key + self.markallow=true +end + +--- Disable assigning targets by placing markers on the F10 map. +-- @param #ARTY self +function ARTY:SetMarkTargetsOff() + self.markallow=false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Start Event ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1012,23 +1039,233 @@ function ARTY:onafterStart(Controllable, From, Event, To) self:__Status(self.StatusInterval) end +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text to be analyzed. +-- @return #table Table with assignment parameters, e.g. number of shots, radius, time etc. +function ARTY:_Markertext(text) + self:F(text) + + -- Assignment parameters. + local assignment={} + assignment.battery={} + assignment.move=false + assignment.engage=false + assignment.time=nil + assignment.nshells=nil + assignment.prio=nil + assignment.maxengage=nil + assignment.radius=nil + assignment.weapontype=nil + assignment.speed=nil + assignment.onroad=nil + assignment.key=nil + + if text:lower():find("arty") then + env.info("FF: Found arty command:") + + if text:lower():find("engage") then + assignment.engage=true + elseif text:lower():find("move") then + assignment.move=true + else + self:E(ARTY.id.."ERROR: Neither ENGAGE nor MOVE keyword specified!") + return + end + + -- keywords are split by "," + local keywords=self:_split(text, ",") + + for _,key in pairs(keywords) do + + local s=self:_split(key, " ") + local val=s[2] + + -- Battery name, i.e. which ARTY group should fire. + if key:lower():find("battery") then + + local v=self:_split(text, '"') + + table.insert(assignment.battery, v[2]) + env.info(string.format("FF: Battery=%s.", v[2])) + + elseif key:lower():find("time") then + + if val:lower():find("now") then + assignment.time=self:_SecondsToClock(timer.getTime0()+5) + else + assignment.time=val + end + env.info(string.format("FF: Time=%s.", val)) + + elseif key:lower():find("shots") then + + assignment.nshells=tonumber(s[2]) + env.info(string.format("FF: Shots=%s.", val)) + + elseif key:lower():find("prio") then + + assignment.prio=tonumber(val) + env.info(string.format("FF: Prio=%s.", val)) + + elseif key:lower():find("maxengage") then + + assignment.maxengage=tonumber(val) + env.info(string.format("FF: Maxengage=%s.", val)) + + elseif key:lower():find("radius") then + + assignment.radius=tonumber(val) + env.info(string.format("Radius=%s.", val)) + + elseif key:lower():find("weapon") then + + if val:lower():find("cannon") then + assignment.weapontype=ARTY.WeaponType.Cannon + elseif val:lower():find("rocket") then + assignment.weapontype=ARTY.WeaponType.Rockets + elseif val:lower():find("missile") then + assignment.weapontype=ARTY.WeaponType.GuidedMissile + elseif val:lower():find("nuke") then + assignment.weapontype=ARTY.WeaponType.TacticalNukes + else + assignment.weapontype=ARTY.WeaponType.Auto + end + env.info(string.format("FF: Weapon=%s.", val)) + + elseif key:lower():find("speed") then + + assignment.speed=tonumber(val) + env.info(string.format("FF: Speed=%s.", val)) + + elseif key:lower():find("road") then + + assignment.onroad=true + env.info(string.format("FF: Onroad=true.")) + + elseif key:lower():find("key") then + + assignment.key=tonumber(val) + env.info(string.format("FF: Key=%s.", val)) + + end + + end + else + env.info("FF: This is NO arty command!") + end + + return assignment +end + --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self -- @param #table Event -function ART:onEvent(Event) +function ARTY:onEvent(Event) - if Event then + if Event == nil or Event.idx == nil then + self:T3("Skipping onEvent. Event or Event.idx unknown.") + return true + end + + local batteryname=self.Controllable:GetName() + local batterycoalition=self.Controllable:GetCoalition() - if Event.id==world.event.S_EVENT_MARK_ADDED then - env.info("FF mark added") - elseif Event.id==world.event.S_EVENT_MARK_CHANGE then - env.info("FF mark changed") - elseif Event.id==world.event.S_EVENT_MARK_REMOVED then - env.info("FF mark removed") + env.info(string.format("Event captured = %s", tostring(batteryname))) + env.info(string.format("Event id = %s", tostring(Event.id))) + env.info(string.format("Event time = %s", tostring(Event.time))) + env.info(string.format("Event idx = %s", tostring(Event.idx))) + env.info(string.format("Event coalition = %s", tostring(Event.coalition))) + env.info(string.format("Event group id = %s", tostring(Event.groupID))) + env.info(string.format("Event text = %s", tostring(Event.text))) + self:E({eventid=Event.id, vec3=Event.pos}) + if Event.initiator~=nil then + local _unitname=Event.initiator:getName() + env.info(string.format("Event ini unit name = %s", tostring(_unitname))) + end + + + if Event.id==world.event.S_EVENT_MARK_ADDED then + self:E({event="S_EVENT_MARK_ADDED", vec3=Event.pos}) + + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + self:E({event="S_EVENT_MARK_CHANGE", vec3=Event.pos}) + + -- Check if marker has a text and the "arty" keyword. + if Event.text~=nil and Event.text:lower():find("arty") then + + -- Check if we have the right coalition and text has arty keyword. + if batterycoalition==Event.coalition or self.markkey~=nil then + + -- Evaluate marker text and extract parameters. + local _assign=self:_Markertext(Event.text) + + local _n=#_assign.battery + env.info("FF: number of batteries assigned to target = ".._n) + + -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. + local _assigned=true + if _n>0 then + _assigned=false + for _,bat in pairs(_assign.battery) do + env.info(string.format("FF: compare %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) + if batteryname==bat then + _assigned=true + end + end + end + + -- We are meant. + if _assigned then + + -- Convert (wrong x-->z, z-->x) vec3 + local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} + -- Get coordinate from vec3. + local _coord=COORDINATE:NewFromVec3(vec3) + + if _assign.move then + + -- Create a new name. + local _name=string.format("Marked Move ID=%d for battery %s", Event.idx, batteryname) + self:E(ARTY.id.._name) + + -- Assign a relocation of the arty group. + self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) + + else + + -- Create a new name. + local _name=string.format("Marked Target ID=%d for battery %s", Event.idx, batteryname) + self:E(ARTY.id.._name) + + -- Assign a new firing engagement. + self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) + + end + end + + end + end + + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + self:E({event="S_EVENT_MARK_REMOVED", vec3=Event.pos}) + + -- Check if we have the right coalition. + if batterycoalition==Event.coalition and Event.text:lower():find("arty") then + + -- This should be the unique name of the target or move. + + if Event.text:lower():find("move") then + local _name=string.format("Marked Move ID=%d for battery %s", Event.idx, batteryname) + self:RemoveMove(_name) + else + local _name=string.format("Marked Target ID=%d for battery %s", Event.idx, batteryname) + self:RemoveTarget(_name) + end end end - + end --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. @@ -1037,6 +1274,12 @@ function ARTY:_StatusReport() -- Get Ammo. local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() + local Nnukes + if self.Nukes==nil then + Nnukes=0 + else + Nnukes=self.Nukes + end local Tnow=timer.getTime() local Clock=self:_SecondsToClock(timer.getAbsTime()) @@ -1048,7 +1291,7 @@ function ARTY:_StatusReport() 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 missiles = %d\n", Nmissiles) - text=text..string.format("Number of nukes = %d\n", self.Nukes) + text=text..string.format("Number of nukes = %d\n", Nnukes) if self.currentTarget then 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) From da452ed8ce07a7c1636fcb019beb437e4ee4fe68 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 30 May 2018 21:17:09 +0200 Subject: [PATCH 03/16] ARTY v0.9.7 Improved Mark Target Assignments. Improved documentation. --- .../Moose/Functional/Artillery.lua | 159 +++++++++++++++--- 1 file changed, 132 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 4ecae201b..0cbc4474d 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -12,6 +12,8 @@ -- * Special weapon types can be selected for each attack, e.g. cruise missiles for Naval units. -- * Automatic rearming once the artillery is out of ammo. -- * New targets can be added during the mission, e.g. when they are detected by recon units. +-- * Modeling of tactical nuclear shells. +-- * Targets and relocations can be assigned by placing markers on the F10 map. -- * Finite state machine implementation. Mission designer can interact when certain events occur. -- -- ==== @@ -192,9 +194,9 @@ -- Unfortunately, there is no easy way to count only those ammo types useable as artillery. Therefore, to keep the implementation general the user -- can specify the names of the ammo types by the following functions: -- --- * @{#ARTY.SetShellTypes}(*tableofnames*): Defines the ammo types for unguided cannons. Default is *tableofnames*={"weapons.shells"}, i.e. **all** types of shells are counted. --- * @{#ARTY.SetRocketTypes}(*tableofnames*): Defines the ammo types of unguided rockets. Default is *tableofnames*={"weapons.nurs"}, i.e. **all** types of rockets are counted. --- * @{#ARTY.SetMissileTypes}(*tableofnames*): Defines the ammo types of guided missiles. Default is *tableofnames*={"weapons.missiles"}, i.e. **all** types of missiles are counted. +-- * @{#ARTY.SetShellTypes}(*tableofnames*): Defines the ammo types for unguided cannons, e.g. *tableofnames*={"weapons.shells"}, i.e. **all** types of shells are counted. +-- * @{#ARTY.SetRocketTypes}(*tableofnames*): Defines the ammo types of unguided rockets, e.g. *tableofnames*={"weapons.nurs"}, i.e. **all** types of rockets are counted. +-- * @{#ARTY.SetMissileTypes}(*tableofnames*): Defines the ammo types of guided missiles, e.g. is *tableofnames*={"weapons.missiles"}, i.e. **all** types of missiles are counted. -- -- **Note** that the default parameters "weapons.shells", "weapons.nurs", "weapons.missiles" **should in priciple** capture all the corresponding ammo types. -- However, the logic searches for the string "weapon.missies" in the ammo type. Especially for missiles, this string is often not contained in the ammo type descriptor. @@ -253,10 +255,66 @@ -- -- After the rearming is complete, both groups will move back to their original positions. -- +-- ## Tactical Nukes +-- +-- ARTY groups that can fire shells can also be used to fire tactical nukes. This is simply achieved by setting the weapon type to **ARTY.WeaponType.TacticalNukes** in the +-- @{#ARTY.AssignTargetCoord}() function. +-- +-- The default explostion strength is 0.075 kilo tons TNT. The can be changed with the @{#ARTY.SetTacNukeWarhead}(*strength*), where *strength* is given in kilo tons TNT. +-- +-- By default, all available conventional shells can be used as nuclear shells. However, it is possible to restrict the number with the @{#ARTY.SetTacNukeShells}(*n*) function +-- to only have *n* nuclear shells available. Note that the group must always have convenctional shells left in order to fire a nuclear shell. +-- +-- ## Assignments via Markers on F10 Map +-- +-- Targets and relocations can be assigned by players via placing a mark on the F10 map. The marker text must contain certain keywords. +-- +-- This feature can be turned on with the @{#ARTY.SetMarkAssignmentsOn}(*key*). The parameter *key* is optional. When set, it can be used as PIN, i.e. only +-- player who know the correct key are able to assign targets or relocations. Default behavior is that all players belonging to the same coalition as the +-- ARTY group are able to assign targets and moves. +-- +-- ### Target Assignments +-- A new target can be assigned by writing **arty engage** in the marker text. This can be followed by a comma separated lists of optional keywords and parameters: +-- +-- * *time* Time for which which the engagement is schedules, e.g. 08:42. Default is as soon as possible. +-- * *prio* Priority of the engagement as number between 1 (high prio) and 100 (low prio). Default is 50. +-- * *shots* Number of shots (shells, rockets or missiles) fired at each engagement. Default is 5. +-- * *engage* Number of times the target is engaged. Default is 1. +-- * *radius* Scattering radius of the fired shots in meters. Default is 100 m. +-- * *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. +-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. +-- +-- Here are examples of valid marker texts: +-- arty engage! +-- arty engage! shots 20, prio 10, time 08:15, weapon cannons +-- arty engage! battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15 +-- arty engage! battery "Blue Paladin 1", key 666 +-- +-- 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. +-- +-- ### Relocation Assignments +-- +-- Markers can also be used to relocate the group with the keyphrase **arty move**. This is done in a similar way as assigning targets. Here, the (optional) keywords and parameters are: +-- +-- * *time* Time for which which the relocation/move is schedules, e.g. 08:42. Default is as soon as possible. +-- * *speed* The speed in km/h the group will drive at. Default is 70% of its max possible speed. +-- * *onroad* Group will use mainly roads. Default is off, i.e. it will go in a straight line from its current position to the assigned coordinate. +-- * *cancel* 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. +-- +-- Here are some examples: +-- arty move! time 23:45, speed 50, onroad, cancel +-- arty move! battery "Blue Paladin", onroad +-- arty move, cancel, speed 10, onroad +-- -- ## Fine Tuning -- -- The mission designer has a few options to tailor the ARTY object according to his needs. -- +-- * @{#ARTY.SetRelocateAfterEngagement}() will cause the ARTY group to change its position after each firing assignment. +-- * @{#ARTY.SetRelocateDistance}(*rmax*, *rmin*) sets the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. -- * @{#ARTY.RemoveAllTargets}() removes all targets from the target queue. -- * @{#ARTY.RemoveTarget}(*name*) deletes the target with *name* from the target queue. -- * @{#ARTY.SetMaxFiringRange}(*range*) defines the maximum firing range. Targets further away than this distance are not engaged. @@ -399,7 +457,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.6" +ARTY.version="0.9.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -793,7 +851,7 @@ function ARTY:SetDebugOFF() self.Debug=false end ---- Delete a target from target list. +--- Delete a target from target list. If the target is currently engaged, it is cancelled. -- @param #ARTY self -- @param #string name Name of the target. function ARTY:RemoveTarget(name) @@ -804,6 +862,12 @@ function ARTY:RemoveTarget(name) table.remove(self.targets, id) end self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.Controllable:GetName(), #self.targets)) + if self.currentTarget then + if self.currentTarget.name==name then + self:T(ARTY.id..string.format("Group %s: Cancelling current target %s (id=%d).", self.Controllable:GetName(), name, id)) + self:CeaseFire(self.currentTarget) + end + end end --- Delete a move from move list. @@ -906,10 +970,10 @@ function ARTY:SetRelocateDistance(rmax, rmin) self.relocateRmin=rmin or 300 end ---- Enable assigning targets by placing markers on the F10 map. +--- Enable assigning targets and moves by placing markers on the F10 map. -- @param #ARTY self -- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. -function ARTY:SetMarkTargetsOn(key) +function ARTY:SetMarkAssignmentsOn(key) self.markkey=key self.markallow=true end @@ -918,6 +982,7 @@ end -- @param #ARTY self function ARTY:SetMarkTargetsOff() self.markallow=false + self.markkey=nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -962,8 +1027,8 @@ function ARTY:onafterStart(Controllable, From, Event, To) 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 default = %d km/h\n", self.Speed) - text=text..string.format("Min range = %d km\n", self.minrange/1000) - text=text..string.format("Max range = %d km\n", self.maxrange/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("Total ammo count = %d\n", self.Nammo0) text=text..string.format("Number of shells = %d\n", self.Nshells0) text=text..string.format("Number of rockets = %d\n", self.Nrockets0) @@ -986,8 +1051,10 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Rearming ARTY roads = %s\n", tostring(self.RearmingArtyOnRoad)) 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("Relocate min dist. = %d m\n", self.relocateRmin) + text=text..string.format("Relocate max dist. = %d m\n", self.relocateRmax) + text=text..string.format("Marker assignments = %s\n", tostring(self.markallow)) + text=text..string.format("Marker auth. key = %s\n", tostring(self.markkey)) text=text..string.format("******************************************************\n") text=text..string.format("Targets:\n") for _, target in pairs(self.targets) do @@ -1055,9 +1122,7 @@ function ARTY:_Markertext(text) assignment.onroad=nil assignment.key=nil - if text:lower():find("arty") then - env.info("FF: Found arty command:") - + if text:lower():find("arty") then if text:lower():find("engage") then assignment.engage=true elseif text:lower():find("move") then @@ -1071,6 +1136,7 @@ function ARTY:_Markertext(text) local keywords=self:_split(text, ",") for _,key in pairs(keywords) do + --env.info("key="..key) local s=self:_split(key, " ") local val=s[2] @@ -1078,10 +1144,12 @@ function ARTY:_Markertext(text) -- Battery name, i.e. which ARTY group should fire. if key:lower():find("battery") then - local v=self:_split(text, '"') - - table.insert(assignment.battery, v[2]) - env.info(string.format("FF: Battery=%s.", v[2])) + local v=self:_split(key, '"') + + for i=2,#v,2 do + table.insert(assignment.battery, v[i]) + env.info(string.format("FF: Battery=%s.", v[i])) + end elseif key:lower():find("time") then @@ -1110,7 +1178,7 @@ function ARTY:_Markertext(text) elseif key:lower():find("radius") then assignment.radius=tonumber(val) - env.info(string.format("Radius=%s.", val)) + env.info(string.format("FF: Radius=%s.", val)) elseif key:lower():find("weapon") then @@ -1188,18 +1256,15 @@ function ARTY:onEvent(Event) -- Check if marker has a text and the "arty" keyword. if Event.text~=nil and Event.text:lower():find("arty") then - -- Check if we have the right coalition and text has arty keyword. - if batterycoalition==Event.coalition or self.markkey~=nil then + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then -- Evaluate marker text and extract parameters. local _assign=self:_Markertext(Event.text) - - local _n=#_assign.battery - env.info("FF: number of batteries assigned to target = ".._n) - + -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. local _assigned=true - if _n>0 then + if #_assign.battery>0 then _assigned=false for _,bat in pairs(_assign.battery) do env.info(string.format("FF: compare %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) @@ -1209,8 +1274,27 @@ function ARTY:onEvent(Event) end end + -- Check if the authorization key is required and if it is valid. + local _validkey=true + if self.markkey~=nil then + _validkey=false + if _assign.key~=nil then + _validkey=self.markkey==_assign.key + end + self:T(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(_assign.key), tostring(_validkey))) + local text="" + if _assign.key==nil then + text=string.format("%s, authorization required but did not receive a key!", batteryname) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(_assign.key)) + elseif _validkey==true then + text=string.format("%s, authorization successful!", batteryname) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + -- We are meant. - if _assigned then + if _assigned and _validkey then -- Convert (wrong x-->z, z-->x) vec3 local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} @@ -1219,6 +1303,9 @@ function ARTY:onEvent(Event) if _assign.move then + local text=string.format("%s, received new relocation assignment.", batteryname) + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + -- Create a new name. local _name=string.format("Marked Move ID=%d for battery %s", Event.idx, batteryname) self:E(ARTY.id.._name) @@ -1228,6 +1315,24 @@ function ARTY:onEvent(Event) else + local text=string.format("%s, received new target assignment.", batteryname) + if _assign.time then + text=text..string.format("\nTime %s",_assign.time) + end + if _assign.prio then + text=text..string.format("\nPrio %d",_assign.prio) + end + if _assign.nshells then + text=text..string.format("\nShots %d",_assign.nshells) + end + if _assign.maxengage then + text=text..string.format("\nEngagements %d",_assign.maxengage) + end + if _assign.weapontype then + text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + -- Create a new name. local _name=string.format("Marked Target ID=%d for battery %s", Event.idx, batteryname) self:E(ARTY.id.._name) From 0b959306743e1b86580111bfa1f28bfb366d9edc Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 1 Jun 2018 23:51:53 +0200 Subject: [PATCH 04/16] ARTY v0.9.8 Improved maker targets and moves assignments. Removed env.info() --- Moose Development/Moose/Core/Point.lua | 42 +- .../Moose/Functional/Artillery.lua | 437 ++++++++++++------ 2 files changed, 325 insertions(+), 154 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0a90f6440..23dfae5ad 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1153,13 +1153,19 @@ do -- COORDINATE --- Mark to All -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" ) - function COORDINATE:MarkToAll( MarkText ) + function COORDINATE:MarkToAll( MarkText, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text) return MarkID end @@ -1167,50 +1173,66 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. -- @param Coalition + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED ) - function COORDINATE:MarkToCoalition( MarkText, Coalition ) + function COORDINATE:MarkToCoalition( MarkText, Coalition, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, ReadOnly, text ) return MarkID end --- Mark to Red Coalition -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalitionRed( "This is a target for the red coalition" ) - function COORDINATE:MarkToCoalitionRed( MarkText ) - return self:MarkToCoalition( MarkText, coalition.side.RED ) + function COORDINATE:MarkToCoalitionRed( MarkText, ReadOnly, Text ) + return self:MarkToCoalition( MarkText, coalition.side.RED, ReadOnly, Text ) end --- Mark to Blue Coalition -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalitionBlue( "This is a target for the blue coalition" ) - function COORDINATE:MarkToCoalitionBlue( MarkText ) - return self:MarkToCoalition( MarkText, coalition.side.BLUE ) + function COORDINATE:MarkToCoalitionBlue( MarkText, ReadOnly, Text ) + return self:MarkToCoalition( MarkText, coalition.side.BLUE, ReadOnly, Text ) end --- Mark to Group -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. -- @param Wrapper.Group#GROUP MarkGroup The @{Wrapper.Group} that receives the mark. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkGroup = GROUP:FindByName( "AttackGroup" ) -- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup ) - function COORDINATE:MarkToGroup( MarkText, MarkGroup ) + function COORDINATE:MarkToGroup( MarkText, MarkGroup, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), ReadOnly, text ) return MarkID end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 0cbc4474d..2857d755a 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -75,7 +75,7 @@ -- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets. -- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles. -- @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.1 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. @@ -86,7 +86,8 @@ -- @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. -- @field #boolean markallow If true, Players are allowed to assign targets and moves for ARTY group by placing markers on the F10 map. Default is false. --- @field #number markkey Authorization key. Only player who know this key can assign targets and moves via markers on the F10 map. Default no authorization required. +-- @field #number markkey Authorization key. Only player who know this key can assign targets and moves via markers on the F10 map. Default no authorization required. +-- @field #boolean markreadonly Marks for targets are readonly and cannot be removed by players. Default is false. -- @extends Core.Fsm#FSM_CONTROLLABLE --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can @@ -424,7 +425,7 @@ ARTY={ ammorockets={}, ammomissiles={}, Nshots=0, - minrange=500, + minrange=100, maxrange=1000000, nukewarhead=75000, Nukes=nil, @@ -436,6 +437,7 @@ ARTY={ relocateRmax=800, markallow=false, markkey=nil, + markreadonly=false, } --- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag @@ -457,7 +459,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.7" +ARTY.version="0.9.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -665,7 +667,7 @@ function ARTY:AssignTargetCoord(coord, prio, radius, nshells, maxengage, time, w table.insert(self.targets, _target) -- Trigger new target event. - self:NewTarget(_target) + self:__NewTarget(1, _target) return _name end @@ -732,21 +734,18 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) -- Prepare move array. 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. table.insert(self.moves, _move) + return _name end --- Set minimum firing range. Targets closer than this distance are not engaged. -- @param #ARTY self --- @param #number range Min range in kilometers. Default is 0.5 km. +-- @param #number range Min range in kilometers. Default is 0.1 km. function ARTY:SetMinFiringRange(range) self:F({range=range}) - self.minrange=range*1000 or 500 + self.minrange=range*1000 or 100 end --- Set maximum firing range. Targets further away than this distance are not engaged. @@ -864,7 +863,7 @@ function ARTY:RemoveTarget(name) self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.Controllable:GetName(), #self.targets)) if self.currentTarget then if self.currentTarget.name==name then - self:T(ARTY.id..string.format("Group %s: Cancelling current target %s (id=%d).", self.Controllable:GetName(), name, id)) + self:T(ARTY.id..string.format("Group %s: Cancelling current target %s.", self.Controllable:GetName(), name)) self:CeaseFire(self.currentTarget) end end @@ -881,6 +880,12 @@ function ARTY:RemoveMove(name) table.remove(self.moves, id) end self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.Controllable:GetName(), #self.moves)) + if self.currentMove then + if self.currentMove.name==name then + self:T(ARTY.id..string.format("Group %s: Cancelling current move %s.", self.Controllable:GetName(), name)) + self:Arrived() + end + end end --- Delete ALL targets from current target list. @@ -973,9 +978,13 @@ end --- Enable assigning targets and moves by placing markers on the F10 map. -- @param #ARTY self -- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. -function ARTY:SetMarkAssignmentsOn(key) +-- @param #boolean readonly (Optional) Marks are readonly and cannot be removed by players. This also means that targets cannot be cancelled by removing the mark. Default false. +function ARTY:SetMarkAssignmentsOn(key, readonly) self.markkey=key self.markallow=true + if readonly==nil then + self.markreadonly=false + end end --- Disable assigning targets by placing markers on the F10 map. @@ -1055,6 +1064,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Relocate max dist. = %d m\n", self.relocateRmax) text=text..string.format("Marker assignments = %s\n", tostring(self.markallow)) text=text..string.format("Marker auth. key = %s\n", tostring(self.markkey)) + text=text..string.format("Marker readonly = %s\n", tostring(self.markreadonly)) text=text..string.format("******************************************************\n") text=text..string.format("Targets:\n") for _, target in pairs(self.targets) do @@ -1112,6 +1122,7 @@ function ARTY:_Markertext(text) assignment.battery={} assignment.move=false assignment.engage=false + assignment.readonly=false assignment.time=nil assignment.nshells=nil assignment.prio=nil @@ -1136,7 +1147,6 @@ function ARTY:_Markertext(text) local keywords=self:_split(text, ",") for _,key in pairs(keywords) do - --env.info("key="..key) local s=self:_split(key, " ") local val=s[2] @@ -1148,7 +1158,7 @@ function ARTY:_Markertext(text) for i=2,#v,2 do table.insert(assignment.battery, v[i]) - env.info(string.format("FF: Battery=%s.", v[i])) + self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) end elseif key:lower():find("time") then @@ -1158,27 +1168,27 @@ function ARTY:_Markertext(text) else assignment.time=val end - env.info(string.format("FF: Time=%s.", val)) + self:T2(ARTY.id..string.format("Key Time=%s.", val)) elseif key:lower():find("shots") then assignment.nshells=tonumber(s[2]) - env.info(string.format("FF: Shots=%s.", val)) + self:T(ARTY.id..string.format("Key Shots=%s.", val)) elseif key:lower():find("prio") then assignment.prio=tonumber(val) - env.info(string.format("FF: Prio=%s.", val)) + self:T2(string.format("Key Prio=%s.", val)) elseif key:lower():find("maxengage") then assignment.maxengage=tonumber(val) - env.info(string.format("FF: Maxengage=%s.", val)) + self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) elseif key:lower():find("radius") then assignment.radius=tonumber(val) - env.info(string.format("FF: Radius=%s.", val)) + self:T2(ARTY.id..string.format("Key Radius=%s.", val)) elseif key:lower():find("weapon") then @@ -1193,28 +1203,31 @@ function ARTY:_Markertext(text) else assignment.weapontype=ARTY.WeaponType.Auto end - env.info(string.format("FF: Weapon=%s.", val)) + self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) elseif key:lower():find("speed") then assignment.speed=tonumber(val) - env.info(string.format("FF: Speed=%s.", val)) + self:T2(ARTY.id..string.format("Key Speed=%s.", val)) elseif key:lower():find("road") then assignment.onroad=true - env.info(string.format("FF: Onroad=true.")) + self:T2(ARTY.id..string.format("Key Onroad=true.")) elseif key:lower():find("key") then assignment.key=tonumber(val) - env.info(string.format("FF: Key=%s.", val)) + self:T(ARTY.id..string.format("Key Key=%s.", val)) + elseif key:lower():find("irrevocable") then + assignment.readonly=true + self:T2(ARTY.id..string.format("Key Readonly=true.")) end end else - env.info("FF: This is NO arty command!") + self:T2(ARTY.id..string.format("This is NO arty command:\n%s", tostring(text))) end return assignment @@ -1230,143 +1243,275 @@ function ARTY:onEvent(Event) return true end + -- Set battery and coalition. local batteryname=self.Controllable:GetName() local batterycoalition=self.Controllable:GetCoalition() - env.info(string.format("Event captured = %s", tostring(batteryname))) - env.info(string.format("Event id = %s", tostring(Event.id))) - env.info(string.format("Event time = %s", tostring(Event.time))) - env.info(string.format("Event idx = %s", tostring(Event.idx))) - env.info(string.format("Event coalition = %s", tostring(Event.coalition))) - env.info(string.format("Event group id = %s", tostring(Event.groupID))) - env.info(string.format("Event text = %s", tostring(Event.text))) + self:T(string.format("Event captured = %s", tostring(batteryname))) + self:T(string.format("Event id = %s", tostring(Event.id))) + self:T(string.format("Event time = %s", tostring(Event.time))) + self:T(string.format("Event idx = %s", tostring(Event.idx))) + self:T(string.format("Event coalition = %s", tostring(Event.coalition))) + self:T(string.format("Event group id = %s", tostring(Event.groupID))) + self:T(string.format("Event text = %s", tostring(Event.text))) self:E({eventid=Event.id, vec3=Event.pos}) if Event.initiator~=nil then local _unitname=Event.initiator:getName() - env.info(string.format("Event ini unit name = %s", tostring(_unitname))) + self:T(string.format("Event ini unit name = %s", tostring(_unitname))) end - if Event.id==world.event.S_EVENT_MARK_ADDED then - self:E({event="S_EVENT_MARK_ADDED", vec3=Event.pos}) + self:E({event="S_EVENT_MARK_ADDED", battery=batteryname, vec3=Event.pos}) elseif Event.id==world.event.S_EVENT_MARK_CHANGE then - self:E({event="S_EVENT_MARK_CHANGE", vec3=Event.pos}) + self:E({event="S_EVENT_MARK_CHANGE", battery=batteryname, vec3=Event.pos}) - -- Check if marker has a text and the "arty" keyword. - if Event.text~=nil and Event.text:lower():find("arty") then - - -- Check if the coalition is the same or an authorization key has been defined. - if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then - - -- Evaluate marker text and extract parameters. - local _assign=self:_Markertext(Event.text) - - -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. - local _assigned=true - if #_assign.battery>0 then - _assigned=false - for _,bat in pairs(_assign.battery) do - env.info(string.format("FF: compare %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) - if batteryname==bat then - _assigned=true - end - end - end - - -- Check if the authorization key is required and if it is valid. - local _validkey=true - if self.markkey~=nil then - _validkey=false - if _assign.key~=nil then - _validkey=self.markkey==_assign.key - end - self:T(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(_assign.key), tostring(_validkey))) - local text="" - if _assign.key==nil then - text=string.format("%s, authorization required but did not receive a key!", batteryname) - elseif _validkey==false then - text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(_assign.key)) - elseif _validkey==true then - text=string.format("%s, authorization successful!", batteryname) - end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) - end - - -- We are meant. - if _assigned and _validkey then - - -- Convert (wrong x-->z, z-->x) vec3 - local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} - -- Get coordinate from vec3. - local _coord=COORDINATE:NewFromVec3(vec3) - - if _assign.move then - - local text=string.format("%s, received new relocation assignment.", batteryname) - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) - - -- Create a new name. - local _name=string.format("Marked Move ID=%d for battery %s", Event.idx, batteryname) - self:E(ARTY.id.._name) - - -- Assign a relocation of the arty group. - self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) - - else - - local text=string.format("%s, received new target assignment.", batteryname) - if _assign.time then - text=text..string.format("\nTime %s",_assign.time) - end - if _assign.prio then - text=text..string.format("\nPrio %d",_assign.prio) - end - if _assign.nshells then - text=text..string.format("\nShots %d",_assign.nshells) - end - if _assign.maxengage then - text=text..string.format("\nEngagements %d",_assign.maxengage) - end - if _assign.weapontype then - text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) - end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) - - -- Create a new name. - local _name=string.format("Marked Target ID=%d for battery %s", Event.idx, batteryname) - self:E(ARTY.id.._name) - - -- Assign a new firing engagement. - self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) - - end - end - - end - end + -- Handle event. + self:_OnEventMarkChange(Event) elseif Event.id==world.event.S_EVENT_MARK_REMOVED then - self:E({event="S_EVENT_MARK_REMOVED", vec3=Event.pos}) - - -- Check if we have the right coalition. - if batterycoalition==Event.coalition and Event.text:lower():find("arty") then - - -- This should be the unique name of the target or move. - - if Event.text:lower():find("move") then - local _name=string.format("Marked Move ID=%d for battery %s", Event.idx, batteryname) - self:RemoveMove(_name) - else - local _name=string.format("Marked Target ID=%d for battery %s", Event.idx, batteryname) - self:RemoveTarget(_name) - end - end + self:E({event="S_EVENT_MARK_REMOVED", battery=batteryname, vec3=Event.pos}) + -- Hande event. + self:_OnEventMarkRemove(Event) end end +--- Function called when a F10 map mark was removed. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkRemove(Event) + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + local batteryname=self.Controllable:GetName() + + if Event.text~=nil and Event.text:find("BATTERY") then + + local _cancelmove=false + local _canceltarget=false + local _name="" + local _id=nil + if Event.text:find("Marked Relocation") then + _cancelmove=true + _name=string.format("BATTERY %s Marked Relocation ID=%d", batteryname, Event.idx) + _id=self:_GetMoveIndexByName(_name) + elseif Event.text:find("Marked Target") then + _canceltarget=true + _name=string.format("BATTERY %s Marked Target ID=%d", batteryname, Event.idx) + _id=self:_GetTargetIndexByName(_name) + else + return + end + + if _id==nil then + return + end + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Get assignment. + local mykey=nil + if self.markkey~=nil then + -- keywords are split by "," + local keywords=self:_split(Event.text, ",") + for _,key in pairs(keywords) do + local s=self:_split(key, " ") + local val=s[2] + if key:lower():find("key") then + mykey=tonumber(val) + self:T(ARTY.id..string.format("Key Key=%s.", val)) + end + end + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=true + if self.markkey~=nil then + _validkey=false + if mykey~=nil then + _validkey=self.markkey==mykey + end + self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) + local text="" + if mykey==nil then + text=string.format("%s, authorization required but did not receive a key!", batteryname) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(mykey)) + elseif _validkey==true then + text=string.format("%s, authentification successful!", batteryname) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + -- Check if we have the right coalition. + if _validkey then + + -- This should be the unique name of the target or move. + if _cancelmove then + self:RemoveMove(_name) + elseif _canceltarget then + self:RemoveTarget(_name) + end + + end + + end + + end +end + +--- Function called when a F10 map mark was changed. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkChange(Event) + + -- Check if marker has a text and the "arty" keyword. + if Event.text~=nil and Event.text:lower():find("arty") then + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + local batteryname=self.Controllable:GetName() + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Evaluate marker text and extract parameters. + local _assign=self:_Markertext(Event.text) + + -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. + local _assigned=true + if #_assign.battery>0 then + _assigned=false + for _,bat in pairs(_assign.battery) do + self:T2(ARTY.id..string.format("Compare battery names %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) + if batteryname==bat then + _assigned=true + end + end + end + + -- Check if ENGAGE or MOVE keywords were found. + if not (_assign.engage or _assign.move) or (not _assigned) then + return + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=true + if self.markkey~=nil then + _validkey=false + if _assign.key~=nil then + _validkey=self.markkey==_assign.key + end + self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(_assign.key), tostring(_validkey))) + local text="" + if _assign.key==nil then + text=string.format("%s, authorization required but did not receive a key!", batteryname) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(_assign.key)) + elseif _validkey==true then + text=string.format("%s, authentification successful!", batteryname) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + -- We are meant. + if _validkey then + + -- Convert (wrong x-->z, z-->x) vec3 + local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} + + -- Get coordinate from vec3. + local _coord=COORDINATE:NewFromVec3(vec3) + + -- Remove old mark because it might contain confidential data such as the key. + -- Also I don't know who can see the mark which was created. + _coord:RemoveMark(Event.idx) + + local _id=UTILS._MarkID+1 + + if _assign.move then + + -- Create a new name. This determins the string we search when deleting a move! + local _name=string.format("BATTERY %s Marked Relocation ID=%d", batteryname, _id) + self:E(ARTY.id.._name) + + local text=string.format("%s, received new relocation assignment.", batteryname) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a relocation of the arty group. + local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) + + if _movename~=nil then + local _mid=self:_GetMoveIndexByName(_movename) + local _move=self.moves[_mid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_move.time)) + local _road="Off Road" + if _move.onroad==true then + _road="On Road" + end + local _markertext=_movename..string.format(", Time %s, Speed %d km/h, %s.", clock, _move.speed, _road) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(100) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + end + + else + + -- Create a new name. + local _name=string.format("BATTERY %s Marked Target ID=%d", batteryname, _id) + self:E(ARTY.id.._name) + + local text=string.format("%s, received new target assignment.", batteryname) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + if _assign.time then + text=text..string.format("\nTime %s",_assign.time) + end + if _assign.prio then + text=text..string.format("\nPrio %d",_assign.prio) + end + if _assign.nshells then + text=text..string.format("\nShots %d",_assign.nshells) + end + if _assign.maxengage then + text=text..string.format("\nEngagements %d",_assign.maxengage) + end + if _assign.weapontype then + text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a new firing engagement. + -- Note, we set unique=true so this target gets only added once. + local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) + + if _targetname~=nil then + local _tid=self:_GetTargetIndexByName(_targetname) + local _target=self.targets[_tid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_target.time)) + local weapon=self:_WeaponTypeName(_target.weapontype) + local _markertext=_targetname..string.format(", Priority %d, Radius=%d m, Shots %d, Engagements=%d, Weapon %s, Time %s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(250) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + end + end + end + + end + end + +end + --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self function ARTY:_StatusReport() @@ -2298,6 +2443,9 @@ function ARTY:onafterArrived(Controllable, From, Event, To) -- Set alarm state to auto. self.Controllable:OptionAlarmStateAuto() + -- Clear Tasks + self.Controllable:ClearTasks() + -- Send message local text=string.format("%s, arrived at destination.", Controllable:GetName()) self:T(ARTY.id..text) @@ -2827,8 +2975,9 @@ function ARTY:_GetTargetIndexByName(name) for i=1,#self.targets do local targetname=self.targets[i].name + self:T(ARTY.id..string.format("Have target with name %s. Index = %d", targetname, i)) if targetname==name then - self:T2(ARTY.id..string.format("Found target with name %s. Index = %d", name, i)) + self:T(ARTY.id..string.format("Found target with name %s. Index = %d", name, i)) return i end end From e9a055219e3036418443d7f2722a8fa9a383bd6e Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 2 Jun 2018 19:13:26 +0200 Subject: [PATCH 05/16] ARTY v0.9.8-buggy Improved marker assignments. There is a bug that makes DCS crash when a group is ordered to move and reaches its destination, i.e. arrives. --- .../Moose/Functional/Artillery.lua | 281 +++++++++++------- 1 file changed, 180 insertions(+), 101 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 2857d755a..03fdf5035 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -459,7 +459,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.8" +ARTY.version="0.9.8-buggy" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -856,17 +856,21 @@ end function ARTY:RemoveTarget(name) self:F2(name) local id=self:_GetTargetIndexByName(name) + if id then + + -- Remove target from table. self:T(ARTY.id..string.format("Group %s: Removing target %s (id=%d).", self.Controllable:GetName(), name, id)) table.remove(self.targets, id) + + -- Delete marker belonging to this engagement. + local batteryname,markTargetID, markMoveID=self:_GetMarkIDfromName(name) + if batteryname==self.Controllable:GetName() and markTargetID~=nil then + COORDINATE:RemoveMark(markTargetID) + end + end self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.Controllable:GetName(), #self.targets)) - if self.currentTarget then - if self.currentTarget.name==name then - self:T(ARTY.id..string.format("Group %s: Cancelling current target %s.", self.Controllable:GetName(), name)) - self:CeaseFire(self.currentTarget) - end - end end --- Delete a move from move list. @@ -875,16 +879,17 @@ end function ARTY:RemoveMove(name) self:F2(name) local id=self:_GetMoveIndexByName(name) + if id then + -- Remove move from table. self:T(ARTY.id..string.format("Group %s: Removing move %s (id=%d).", self.Controllable:GetName(), name, id)) table.remove(self.moves, id) - end - self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.Controllable:GetName(), #self.moves)) - if self.currentMove then - if self.currentMove.name==name then - self:T(ARTY.id..string.format("Group %s: Cancelling current move %s.", self.Controllable:GetName(), name)) - self:Arrived() - end + env.info("FF debug remove move") + -- Delete marker belonging to this relocation move. + --local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) + --if batteryname==self.Controllable:GetName() and markMoveID~=nil then + --COORDINATE:RemoveMark(markMoveID) + --end end end @@ -1110,6 +1115,62 @@ function ARTY:onafterStart(Controllable, From, Event, To) self:__Status(self.StatusInterval) end +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text. +-- @return #boolean If true, authentification successful. +function ARTY:_MarkerKeyAuthentification(text) + + -- Set battery and coalition. + local batteryname=self.Controllable:GetName() + local batterycoalition=self.Controllable:GetCoalition() + + -- Get assignment. + local mykey=nil + if self.markkey~=nil then + + -- keywords are split by "," + local keywords=self:_split(text, ",") + for _,key in pairs(keywords) do + local s=self:_split(key, " ") + local val=s[2] + if key:lower():find("key") then + mykey=tonumber(val) + self:T(ARTY.id..string.format("Authorisation Key=%s.", val)) + end + end + + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=true + + -- Check if group needs authorization. + if self.markkey~=nil then + -- Assume key is incorrect. + _validkey=false + + -- If key was found, check if matches. + if mykey~=nil then + _validkey=self.markkey==mykey + end + self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) + + -- Send message + local text="" + if mykey==nil then + text=string.format("%s, authorization required but did not receive a key!", batteryname) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(mykey)) + elseif _validkey==true then + text=string.format("%s, authentification successful!", batteryname) + end + MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + return _validkey +end + --- Extract engagement assignments and parameters from mark text. -- @param #ARTY self -- @param #string text Marker text to be analyzed. @@ -1123,6 +1184,7 @@ function ARTY:_Markertext(text) assignment.move=false assignment.engage=false assignment.readonly=false + assignment.cancelcurrent=false assignment.time=nil assignment.nshells=nil assignment.prio=nil @@ -1133,7 +1195,7 @@ function ARTY:_Markertext(text) assignment.onroad=nil assignment.key=nil - if text:lower():find("arty") then + if text:lower():find("arty") then if text:lower():find("engage") then assignment.engage=true elseif text:lower():find("move") then @@ -1210,19 +1272,18 @@ function ARTY:_Markertext(text) assignment.speed=tonumber(val) self:T2(ARTY.id..string.format("Key Speed=%s.", val)) - elseif key:lower():find("road") then + elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then assignment.onroad=true self:T2(ARTY.id..string.format("Key Onroad=true.")) - - elseif key:lower():find("key") then - - assignment.key=tonumber(val) - self:T(ARTY.id..string.format("Key Key=%s.", val)) - - elseif key:lower():find("irrevocable") then + + elseif key:lower():find("irrevocable") or key:lower():find("readonly") then assignment.readonly=true self:T2(ARTY.id..string.format("Key Readonly=true.")) + + elseif key:lower():find("cancel current") then + assignment.cancelcurrent=true + self:T2(ARTY.id..string.format("Key Cancel Current=true.")) end end @@ -1278,6 +1339,58 @@ function ARTY:onEvent(Event) end +--- Create a name for an engagement initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of target engagement. +function ARTY:_MarkTargetName(markerid) + return string.format("BATTERY=%s, Marked Target ID=%d", self.Controllable:GetName(), markerid) +end + +--- Create a name for a relocation move initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of relocation move. +function ARTY:_MarkMoveName(markerid) + return string.format("BATTERY=%s, Marked Relocation ID=%d", self.Controllable:GetName(), markerid) +end + +--- Create a name for a relocation move initiated by placing a marker. +-- @param #ARTY self +-- @param #sting name Name of the assignment. +-- @return #string Name of the ARTY group or nil +-- @return #number ID of the marked target or nil. +-- @return #number ID of the marked relocation move or nil +function ARTY:_GetMarkIDfromName(name) + + -- keywords are split by "," + local keywords=self:_split(name, ",") + + local battery=nil + local markTID=nil + local markMID=nil + + for _,key in pairs(keywords) do + + local str=self:_split(key, "=") + local par=str[1] + local val=str[2] + + if par:find("BATTERY") then + battery=val + end + if par:find("Marked Target ID") then + markTID=tonumber(val) + end + if par:find("Marked Relocation ID") then + markMID=tonumber(val) + end + + end + + return battery, markTID, markMID +end + --- Function called when a F10 map mark was removed. -- @param #ARTY self -- @param #table Event Event data. @@ -1293,18 +1406,20 @@ function ARTY:_OnEventMarkRemove(Event) local _canceltarget=false local _name="" local _id=nil + if Event.text:find("Marked Relocation") then _cancelmove=true - _name=string.format("BATTERY %s Marked Relocation ID=%d", batteryname, Event.idx) + _name=self:_MarkMoveName(Event.idx) _id=self:_GetMoveIndexByName(_name) elseif Event.text:find("Marked Target") then _canceltarget=true - _name=string.format("BATTERY %s Marked Target ID=%d", batteryname, Event.idx) + _name=self:_MarkTargetName(Event.idx) _id=self:_GetTargetIndexByName(_name) else return end + -- Check if there is a task which matches. if _id==nil then return end @@ -1312,48 +1427,25 @@ function ARTY:_OnEventMarkRemove(Event) -- Check if the coalition is the same or an authorization key has been defined. if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then - -- Get assignment. - local mykey=nil - if self.markkey~=nil then - -- keywords are split by "," - local keywords=self:_split(Event.text, ",") - for _,key in pairs(keywords) do - local s=self:_split(key, " ") - local val=s[2] - if key:lower():find("key") then - mykey=tonumber(val) - self:T(ARTY.id..string.format("Key Key=%s.", val)) - end - end - end - - -- Check if the authorization key is required and if it is valid. - local _validkey=true - if self.markkey~=nil then - _validkey=false - if mykey~=nil then - _validkey=self.markkey==mykey - end - self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) - local text="" - if mykey==nil then - text=string.format("%s, authorization required but did not receive a key!", batteryname) - elseif _validkey==false then - text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(mykey)) - elseif _validkey==true then - text=string.format("%s, authentification successful!", batteryname) - end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) - end + -- Authentify key + local _validkey=self:_MarkerKeyAuthentification(Event.text) -- Check if we have the right coalition. if _validkey then -- This should be the unique name of the target or move. if _cancelmove then - self:RemoveMove(_name) + if self.currentMove and self.currentMove.name==_name then + self:Arrived() + else + self:RemoveMove(_name) + end elseif _canceltarget then - self:RemoveTarget(_name) + if self.currentTarget and self.currentTarget.name==_name then + self:CeaseFire(self.currentTarget) + else + self:RemoveTarget(_name) + end end end @@ -1397,24 +1489,19 @@ function ARTY:_OnEventMarkChange(Event) if not (_assign.engage or _assign.move) or (not _assigned) then return end - + -- Check if the authorization key is required and if it is valid. - local _validkey=true - if self.markkey~=nil then - _validkey=false - if _assign.key~=nil then - _validkey=self.markkey==_assign.key + local _validkey=self:_MarkerKeyAuthentification(Event.text) + + -- Cancel current target. + if _validkey and _assign.cancelcurrent then + if _assign.move and self.currentMove then + self:Arrived() end - self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(_assign.key), tostring(_validkey))) - local text="" - if _assign.key==nil then - text=string.format("%s, authorization required but did not receive a key!", batteryname) - elseif _validkey==false then - text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(_assign.key)) - elseif _validkey==true then - text=string.format("%s, authentification successful!", batteryname) + if _assign.engage and self.currentTarget then + self:CeaseFire(self.currentTarget) end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + return end -- We are meant. @@ -1430,17 +1517,18 @@ function ARTY:_OnEventMarkChange(Event) -- Also I don't know who can see the mark which was created. _coord:RemoveMark(Event.idx) + -- Anticipate marker ID. + -- WARNING: Make sure, no marks are set until the COORDINATE:MarkToCoalition() is called or the target/move name will be wrong and target cannot be removed by deleting its marker. local _id=UTILS._MarkID+1 if _assign.move then -- Create a new name. This determins the string we search when deleting a move! - local _name=string.format("BATTERY %s Marked Relocation ID=%d", batteryname, _id) - self:E(ARTY.id.._name) + local _name=self:_MarkMoveName(_id) local text=string.format("%s, received new relocation assignment.", batteryname) text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) - MESSAGE:New(text, 20):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. local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) @@ -1451,11 +1539,7 @@ function ARTY:_OnEventMarkChange(Event) -- Create new target name. local clock=tostring(self:_SecondsToClock(_move.time)) - local _road="Off Road" - if _move.onroad==true then - _road="On Road" - end - local _markertext=_movename..string.format(", Time %s, Speed %d km/h, %s.", clock, _move.speed, _road) + local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.", clock, _move.speed, tostring(_move.onroad)) -- Create a new mark. This will trigger the mark added event. local _randomcoord=_coord:GetRandomCoordinateInRadius(100) @@ -1465,8 +1549,7 @@ function ARTY:_OnEventMarkChange(Event) else -- Create a new name. - local _name=string.format("BATTERY %s Marked Target ID=%d", batteryname, _id) - self:E(ARTY.id.._name) + local _name=self:_MarkTargetName(_id) local text=string.format("%s, received new target assignment.", batteryname) text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) @@ -1476,6 +1559,9 @@ function ARTY:_OnEventMarkChange(Event) if _assign.prio then text=text..string.format("\nPrio %d",_assign.prio) end + if _assign.prio then + text=text..string.format("\nRadius %d m",_assign.radius) + end if _assign.nshells then text=text..string.format("\nShots %d",_assign.nshells) end @@ -1485,7 +1571,7 @@ function ARTY:_OnEventMarkChange(Event) if _assign.weapontype then text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) -- Assign a new firing engagement. -- Note, we set unique=true so this target gets only added once. @@ -1498,7 +1584,7 @@ function ARTY:_OnEventMarkChange(Event) -- Create new target name. local clock=tostring(self:_SecondsToClock(_target.time)) local weapon=self:_WeaponTypeName(_target.weapontype) - local _markertext=_targetname..string.format(", Priority %d, Radius=%d m, Shots %d, Engagements=%d, Weapon %s, Time %s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) + local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) -- Create a new mark. This will trigger the mark added event. local _randomcoord=_coord:GetRandomCoordinateInRadius(250) @@ -1547,6 +1633,11 @@ function ARTY:_StatusReport() for i=1,#self.targets do text=text..string.format("- %s\n", self:_TargetInfo(self.targets[i])) end + if self.currentMove then + text=text..string.format("Current Move = %s\n", tostring(self.currentMove.name)) + else + text=text..string.format("Current Move = %s\n", "none") + end text=text..string.format("Moves:\n") for i=1,#self.moves do text=text..string.format("- %s\n", self:_MoveInfo(self.moves[i])) @@ -1669,18 +1760,6 @@ function ARTY:_NuclearBlast(_coord) end ---- Eventhandler for shot event. --- @param #ARTY self --- @param Core.Event#EVENTDATA EventData -function ARTY:_OnMarkAdded(EventData) - self:F(EventData) - if EventData.MarkCoordinate then - local coord=EventData.MarkCoordinate --Core.Point#COORDINATE - - coord:SmokeGreen() - end -end - --- Eventhandler for shot event. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData @@ -2453,8 +2532,8 @@ function ARTY:onafterArrived(Controllable, From, Event, To) -- Remove executed move from queue. if self.currentMove then - self:RemoveMove(self.currentMove.name) - self.currentMove=nil + --self:RemoveMove(self.currentMove.name) + --self.currentMove=nil end end From 0d246d3f49841b8d8986be29d6059a4124842fca Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 3 Jun 2018 00:58:24 +0200 Subject: [PATCH 06/16] ARTY v0.9.9 ARTY: Fixed CTD bug. Caused by group:ClearTasks() when no task is assigned (group arrived). Many other improvements Cleared up function location. MESSAGES: Added optional clear screen parameter. --- Moose Development/Moose/Core/Message.lua | 22 +- .../Moose/Functional/Artillery.lua | 1826 +++++++++-------- .../Moose/Wrapper/Controllable.lua | 4 +- 3 files changed, 956 insertions(+), 896 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 926284332..e11f807ea 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -66,6 +66,7 @@ MESSAGE.Type = { -- @param #string MessageText is the text of the Message. -- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. -- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". +-- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage -- -- Create a series of new Messages. @@ -77,7 +78,7 @@ MESSAGE.Type = { -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) -- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) +function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText, MessageDuration, MessageCategory } ) @@ -94,6 +95,11 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) else self.MessageCategory = "" end + + self.ClearScreen=false + if ClearScreen~=nil then + self.ClearScreen=ClearScreen + end self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() @@ -114,18 +120,24 @@ end -- @param self -- @param #string MessageText is the text of the Message. -- @param #MESSAGE.Type MessageType The type of the message. +-- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) -function MESSAGE:NewType( MessageText, MessageType ) +function MESSAGE:NewType( MessageText, MessageType, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText } ) self.MessageType = MessageType + + self.ClearScreen=false + if ClearScreen~=nil then + self.ClearScreen=ClearScreen + end self.MessageTime = timer.getTime() self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) @@ -170,7 +182,7 @@ function MESSAGE:ToClient( Client, Settings ) if self.MessageDuration ~= 0 then local ClientGroupID = Client:GetClientGroupID() self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) end end @@ -194,7 +206,7 @@ function MESSAGE:ToGroup( Group, Settings ) if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end @@ -262,7 +274,7 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings ) if CoalitionSide then if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 03fdf5035..17cdccf94 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -51,7 +51,7 @@ -- @field #number Nshells0 Initial amount of shells 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 Nukes0 Initial amount of tactical nukes of the whole group. +-- @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 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. @@ -459,7 +459,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.8-buggy" +ARTY.version="0.9.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -855,6 +855,8 @@ end -- @param #string name Name of the target. function ARTY:RemoveTarget(name) self:F2(name) + + -- Get target ID from namd local id=self:_GetTargetIndexByName(name) if id then @@ -864,11 +866,13 @@ function ARTY:RemoveTarget(name) table.remove(self.targets, id) -- Delete marker belonging to this engagement. - local batteryname,markTargetID, markMoveID=self:_GetMarkIDfromName(name) - if batteryname==self.Controllable:GetName() and markTargetID~=nil then - COORDINATE:RemoveMark(markTargetID) - end - + if self.markallow then + local batteryname,markTargetID, markMoveID=self:_GetMarkIDfromName(name) + if batteryname==self.Controllable:GetName() and markTargetID~=nil then + COORDINATE:RemoveMark(markTargetID) + end + end + end self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.Controllable:GetName(), #self.targets)) end @@ -878,19 +882,26 @@ end -- @param #string name Name of the target. function ARTY:RemoveMove(name) self:F2(name) + + -- Get move ID from name. local id=self:_GetMoveIndexByName(name) if id then + -- Remove move from table. self:T(ARTY.id..string.format("Group %s: Removing move %s (id=%d).", self.Controllable:GetName(), name, id)) table.remove(self.moves, id) - env.info("FF debug remove move") + -- Delete marker belonging to this relocation move. - --local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) - --if batteryname==self.Controllable:GetName() and markMoveID~=nil then - --COORDINATE:RemoveMark(markMoveID) - --end + if self.markallow then + local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) + if batteryname==self.Controllable:GetName() and markMoveID~=nil then + COORDINATE:RemoveMark(markMoveID) + end + end + end + self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.Controllable:GetName(), #self.moves)) end --- Delete ALL targets from current target list. @@ -1015,7 +1026,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) -- Debug output. local text=string.format("Started ARTY version %s for group %s.", ARTY.version, Controllable:GetName()) self:E(ARTY.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) + MESSAGE:New(text, 5):ToAllIf(self.Debug) -- Get Ammo. self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0=self:GetAmmo(self.Debug) @@ -1027,10 +1038,11 @@ function ARTY:onafterStart(Controllable, From, Event, To) if self.nukefires==nil then self.nukefires=20/1000/1000*self.nukerange*self.nukerange end - if self.Nukes==nil then - self.Nukes0=self.Nshells0 + if self.Nukes~=nil then + self.Nukes0=math.min(self.Nukes, self.Nshells0) else - self.Nukes0=self.Nukes + self.Nukes=0 + self.Nukes0=0 end local text=string.format("\n******************************************************\n") @@ -1106,510 +1118,25 @@ function ARTY:onafterStart(Controllable, From, Event, To) -- Add event handler. self:HandleEvent(EVENTS.Shot, self._OnEventShot) self:HandleEvent(EVENTS.Dead, self._OnEventDead) - self:HandleEvent(EVENTS.MarkAdded, self._OnEventMarkAdded) + --self:HandleEvent(EVENTS.MarkAdded, self._OnEventMarkAdded) - -- Add DCS event handler. - world.addEventHandler(self) + -- Add DCS event handler - necessary for S_EVENT_MARK_* events. So we only start it, if this was requested. + if self.markallow then + world.addEventHandler(self) + end -- Start checking status. self:__Status(self.StatusInterval) end ---- Extract engagement assignments and parameters from mark text. --- @param #ARTY self --- @param #string text Marker text. --- @return #boolean If true, authentification successful. -function ARTY:_MarkerKeyAuthentification(text) - - -- Set battery and coalition. - local batteryname=self.Controllable:GetName() - local batterycoalition=self.Controllable:GetCoalition() - - -- Get assignment. - local mykey=nil - if self.markkey~=nil then - - -- keywords are split by "," - local keywords=self:_split(text, ",") - for _,key in pairs(keywords) do - local s=self:_split(key, " ") - local val=s[2] - if key:lower():find("key") then - mykey=tonumber(val) - self:T(ARTY.id..string.format("Authorisation Key=%s.", val)) - end - end - - end - - -- Check if the authorization key is required and if it is valid. - local _validkey=true - - -- Check if group needs authorization. - if self.markkey~=nil then - -- Assume key is incorrect. - _validkey=false - - -- If key was found, check if matches. - if mykey~=nil then - _validkey=self.markkey==mykey - end - self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) - - -- Send message - local text="" - if mykey==nil then - text=string.format("%s, authorization required but did not receive a key!", batteryname) - elseif _validkey==false then - text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(mykey)) - elseif _validkey==true then - text=string.format("%s, authentification successful!", batteryname) - end - MESSAGE:New(text, 20):ToCoalitionIf(batterycoalition, self.report or self.Debug) - end - - return _validkey -end - ---- Extract engagement assignments and parameters from mark text. --- @param #ARTY self --- @param #string text Marker text to be analyzed. --- @return #table Table with assignment parameters, e.g. number of shots, radius, time etc. -function ARTY:_Markertext(text) - self:F(text) - - -- Assignment parameters. - local assignment={} - assignment.battery={} - assignment.move=false - assignment.engage=false - assignment.readonly=false - assignment.cancelcurrent=false - assignment.time=nil - assignment.nshells=nil - assignment.prio=nil - assignment.maxengage=nil - assignment.radius=nil - assignment.weapontype=nil - assignment.speed=nil - assignment.onroad=nil - assignment.key=nil - - if text:lower():find("arty") then - if text:lower():find("engage") then - assignment.engage=true - elseif text:lower():find("move") then - assignment.move=true - else - self:E(ARTY.id.."ERROR: Neither ENGAGE nor MOVE keyword specified!") - return - end - - -- keywords are split by "," - local keywords=self:_split(text, ",") - - for _,key in pairs(keywords) do - - local s=self:_split(key, " ") - local val=s[2] - - -- Battery name, i.e. which ARTY group should fire. - if key:lower():find("battery") then - - local v=self:_split(key, '"') - - for i=2,#v,2 do - table.insert(assignment.battery, v[i]) - self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) - end - - elseif key:lower():find("time") then - - if val:lower():find("now") then - assignment.time=self:_SecondsToClock(timer.getTime0()+5) - else - assignment.time=val - end - self:T2(ARTY.id..string.format("Key Time=%s.", val)) - - elseif key:lower():find("shots") then - - assignment.nshells=tonumber(s[2]) - self:T(ARTY.id..string.format("Key Shots=%s.", val)) - - elseif key:lower():find("prio") then - - assignment.prio=tonumber(val) - self:T2(string.format("Key Prio=%s.", val)) - - elseif key:lower():find("maxengage") then - - assignment.maxengage=tonumber(val) - self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) - - elseif key:lower():find("radius") then - - assignment.radius=tonumber(val) - self:T2(ARTY.id..string.format("Key Radius=%s.", val)) - - elseif key:lower():find("weapon") then - - if val:lower():find("cannon") then - assignment.weapontype=ARTY.WeaponType.Cannon - elseif val:lower():find("rocket") then - assignment.weapontype=ARTY.WeaponType.Rockets - elseif val:lower():find("missile") then - assignment.weapontype=ARTY.WeaponType.GuidedMissile - elseif val:lower():find("nuke") then - assignment.weapontype=ARTY.WeaponType.TacticalNukes - else - assignment.weapontype=ARTY.WeaponType.Auto - end - self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) - - elseif key:lower():find("speed") then - - assignment.speed=tonumber(val) - self:T2(ARTY.id..string.format("Key Speed=%s.", val)) - - elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then - - assignment.onroad=true - self:T2(ARTY.id..string.format("Key Onroad=true.")) - - elseif key:lower():find("irrevocable") or key:lower():find("readonly") then - assignment.readonly=true - self:T2(ARTY.id..string.format("Key Readonly=true.")) - - elseif key:lower():find("cancel current") then - assignment.cancelcurrent=true - self:T2(ARTY.id..string.format("Key Cancel Current=true.")) - end - - end - else - self:T2(ARTY.id..string.format("This is NO arty command:\n%s", tostring(text))) - end - - return assignment -end - ---- After "Start" event. Initialized ROE and alarm state. Starts the event handler. --- @param #ARTY self --- @param #table Event -function ARTY:onEvent(Event) - - if Event == nil or Event.idx == nil then - self:T3("Skipping onEvent. Event or Event.idx unknown.") - return true - end - - -- Set battery and coalition. - local batteryname=self.Controllable:GetName() - local batterycoalition=self.Controllable:GetCoalition() - - self:T(string.format("Event captured = %s", tostring(batteryname))) - self:T(string.format("Event id = %s", tostring(Event.id))) - self:T(string.format("Event time = %s", tostring(Event.time))) - self:T(string.format("Event idx = %s", tostring(Event.idx))) - self:T(string.format("Event coalition = %s", tostring(Event.coalition))) - self:T(string.format("Event group id = %s", tostring(Event.groupID))) - self:T(string.format("Event text = %s", tostring(Event.text))) - self:E({eventid=Event.id, vec3=Event.pos}) - if Event.initiator~=nil then - local _unitname=Event.initiator:getName() - self:T(string.format("Event ini unit name = %s", tostring(_unitname))) - end - - if Event.id==world.event.S_EVENT_MARK_ADDED then - self:E({event="S_EVENT_MARK_ADDED", battery=batteryname, vec3=Event.pos}) - - elseif Event.id==world.event.S_EVENT_MARK_CHANGE then - self:E({event="S_EVENT_MARK_CHANGE", battery=batteryname, vec3=Event.pos}) - - -- Handle event. - self:_OnEventMarkChange(Event) - - elseif Event.id==world.event.S_EVENT_MARK_REMOVED then - self:E({event="S_EVENT_MARK_REMOVED", battery=batteryname, vec3=Event.pos}) - - -- Hande event. - self:_OnEventMarkRemove(Event) - end - -end - ---- Create a name for an engagement initiated by placing a marker. --- @param #ARTY self --- @param #number markerid ID of the placed marker. --- @return #string Name of target engagement. -function ARTY:_MarkTargetName(markerid) - return string.format("BATTERY=%s, Marked Target ID=%d", self.Controllable:GetName(), markerid) -end - ---- Create a name for a relocation move initiated by placing a marker. --- @param #ARTY self --- @param #number markerid ID of the placed marker. --- @return #string Name of relocation move. -function ARTY:_MarkMoveName(markerid) - return string.format("BATTERY=%s, Marked Relocation ID=%d", self.Controllable:GetName(), markerid) -end - ---- Create a name for a relocation move initiated by placing a marker. --- @param #ARTY self --- @param #sting name Name of the assignment. --- @return #string Name of the ARTY group or nil --- @return #number ID of the marked target or nil. --- @return #number ID of the marked relocation move or nil -function ARTY:_GetMarkIDfromName(name) - - -- keywords are split by "," - local keywords=self:_split(name, ",") - - local battery=nil - local markTID=nil - local markMID=nil - - for _,key in pairs(keywords) do - - local str=self:_split(key, "=") - local par=str[1] - local val=str[2] - - if par:find("BATTERY") then - battery=val - end - if par:find("Marked Target ID") then - markTID=tonumber(val) - end - if par:find("Marked Relocation ID") then - markMID=tonumber(val) - end - - end - - return battery, markTID, markMID -end - ---- Function called when a F10 map mark was removed. --- @param #ARTY self --- @param #table Event Event data. -function ARTY:_OnEventMarkRemove(Event) - - -- Get battery coalition and name. - local batterycoalition=self.Controllable:GetCoalition() - local batteryname=self.Controllable:GetName() - - if Event.text~=nil and Event.text:find("BATTERY") then - - local _cancelmove=false - local _canceltarget=false - local _name="" - local _id=nil - - if Event.text:find("Marked Relocation") then - _cancelmove=true - _name=self:_MarkMoveName(Event.idx) - _id=self:_GetMoveIndexByName(_name) - elseif Event.text:find("Marked Target") then - _canceltarget=true - _name=self:_MarkTargetName(Event.idx) - _id=self:_GetTargetIndexByName(_name) - else - return - end - - -- Check if there is a task which matches. - if _id==nil then - return - end - - -- Check if the coalition is the same or an authorization key has been defined. - if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then - - -- Authentify key - local _validkey=self:_MarkerKeyAuthentification(Event.text) - - -- Check if we have the right coalition. - if _validkey then - - -- This should be the unique name of the target or move. - if _cancelmove then - if self.currentMove and self.currentMove.name==_name then - self:Arrived() - else - self:RemoveMove(_name) - end - elseif _canceltarget then - if self.currentTarget and self.currentTarget.name==_name then - self:CeaseFire(self.currentTarget) - else - self:RemoveTarget(_name) - end - end - - end - - end - - end -end - ---- Function called when a F10 map mark was changed. --- @param #ARTY self --- @param #table Event Event data. -function ARTY:_OnEventMarkChange(Event) - - -- Check if marker has a text and the "arty" keyword. - if Event.text~=nil and Event.text:lower():find("arty") then - - -- Get battery coalition and name. - local batterycoalition=self.Controllable:GetCoalition() - local batteryname=self.Controllable:GetName() - - -- Check if the coalition is the same or an authorization key has been defined. - if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then - - -- Evaluate marker text and extract parameters. - local _assign=self:_Markertext(Event.text) - - -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. - local _assigned=true - if #_assign.battery>0 then - _assigned=false - for _,bat in pairs(_assign.battery) do - self:T2(ARTY.id..string.format("Compare battery names %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) - if batteryname==bat then - _assigned=true - end - end - end - - -- Check if ENGAGE or MOVE keywords were found. - if not (_assign.engage or _assign.move) or (not _assigned) then - return - end - - -- Check if the authorization key is required and if it is valid. - local _validkey=self:_MarkerKeyAuthentification(Event.text) - - -- Cancel current target. - if _validkey and _assign.cancelcurrent then - if _assign.move and self.currentMove then - self:Arrived() - end - if _assign.engage and self.currentTarget then - self:CeaseFire(self.currentTarget) - end - return - end - - -- We are meant. - if _validkey then - - -- Convert (wrong x-->z, z-->x) vec3 - local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} - - -- Get coordinate from vec3. - local _coord=COORDINATE:NewFromVec3(vec3) - - -- Remove old mark because it might contain confidential data such as the key. - -- Also I don't know who can see the mark which was created. - _coord:RemoveMark(Event.idx) - - -- Anticipate marker ID. - -- WARNING: Make sure, no marks are set until the COORDINATE:MarkToCoalition() is called or the target/move name will be wrong and target cannot be removed by deleting its marker. - local _id=UTILS._MarkID+1 - - if _assign.move then - - -- Create a new name. This determins the string we search when deleting a move! - local _name=self:_MarkMoveName(_id) - - local text=string.format("%s, received new relocation assignment.", batteryname) - text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) - MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) - - -- Assign a relocation of the arty group. - local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) - - if _movename~=nil then - local _mid=self:_GetMoveIndexByName(_movename) - local _move=self.moves[_mid] - - -- Create new target name. - local clock=tostring(self:_SecondsToClock(_move.time)) - local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.", clock, _move.speed, tostring(_move.onroad)) - - -- Create a new mark. This will trigger the mark added event. - local _randomcoord=_coord:GetRandomCoordinateInRadius(100) - _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) - end - - else - - -- Create a new name. - local _name=self:_MarkTargetName(_id) - - local text=string.format("%s, received new target assignment.", batteryname) - text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) - if _assign.time then - text=text..string.format("\nTime %s",_assign.time) - end - if _assign.prio then - text=text..string.format("\nPrio %d",_assign.prio) - end - if _assign.prio then - text=text..string.format("\nRadius %d m",_assign.radius) - end - if _assign.nshells then - text=text..string.format("\nShots %d",_assign.nshells) - end - if _assign.maxengage then - text=text..string.format("\nEngagements %d",_assign.maxengage) - end - if _assign.weapontype then - text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) - end - MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) - - -- Assign a new firing engagement. - -- Note, we set unique=true so this target gets only added once. - local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) - - if _targetname~=nil then - local _tid=self:_GetTargetIndexByName(_targetname) - local _target=self.targets[_tid] - - -- Create new target name. - local clock=tostring(self:_SecondsToClock(_target.time)) - local weapon=self:_WeaponTypeName(_target.weapontype) - local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) - - -- Create a new mark. This will trigger the mark added event. - local _randomcoord=_coord:GetRandomCoordinateInRadius(250) - _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) - end - end - end - - end - end - -end - --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self function ARTY:_StatusReport() -- Get Ammo. local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() - local Nnukes - if self.Nukes==nil then - Nnukes=0 - else - Nnukes=self.Nukes - end + local Nnukes=self.Nukes + local Tnow=timer.getTime() local Clock=self:_SecondsToClock(timer.getAbsTime()) @@ -1651,115 +1178,6 @@ end -- 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. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData @@ -1833,8 +1251,7 @@ function ARTY:_OnEventShot(EventData) 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. local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() @@ -1927,6 +1344,278 @@ function ARTY:_OnEventShot(EventData) end end +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #ARTY self +-- @param #table Event +function ARTY:onEvent(Event) + + if Event == nil or Event.idx == nil then + self:T3("Skipping onEvent. Event or Event.idx unknown.") + return true + end + + -- Set battery and coalition. + local batteryname=self.Controllable:GetName() + local batterycoalition=self.Controllable:GetCoalition() + + self:T(string.format("Event captured = %s", tostring(batteryname))) + self:T(string.format("Event id = %s", tostring(Event.id))) + self:T(string.format("Event time = %s", tostring(Event.time))) + self:T(string.format("Event idx = %s", tostring(Event.idx))) + self:T(string.format("Event coalition = %s", tostring(Event.coalition))) + self:T(string.format("Event group id = %s", tostring(Event.groupID))) + self:T(string.format("Event text = %s", tostring(Event.text))) + self:E({eventid=Event.id, vec3=Event.pos}) + if Event.initiator~=nil then + local _unitname=Event.initiator:getName() + self:T(string.format("Event ini unit name = %s", tostring(_unitname))) + end + + if Event.id==world.event.S_EVENT_MARK_ADDED then + self:E({event="S_EVENT_MARK_ADDED", battery=batteryname, vec3=Event.pos}) + + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + self:E({event="S_EVENT_MARK_CHANGE", battery=batteryname, vec3=Event.pos}) + + -- Handle event. + self:_OnEventMarkChange(Event) + + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + self:E({event="S_EVENT_MARK_REMOVED", battery=batteryname, vec3=Event.pos}) + + -- Hande event. + self:_OnEventMarkRemove(Event) + end + +end + +--- Function called when a F10 map mark was removed. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkRemove(Event) + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + local batteryname=self.Controllable:GetName() + + if Event.text~=nil and Event.text:find("BATTERY") then + + -- Init defaults. + local _cancelmove=false + local _canceltarget=false + local _name="" + local _id=nil + + -- Check for key phrases of relocation or engagements in marker text. If not, return. + if Event.text:find("Marked Relocation") then + _cancelmove=true + _name=self:_MarkMoveName(Event.idx) + _id=self:_GetMoveIndexByName(_name) + elseif Event.text:find("Marked Target") then + _canceltarget=true + _name=self:_MarkTargetName(Event.idx) + _id=self:_GetTargetIndexByName(_name) + else + return + end + + -- Check if there is a task which matches. + if _id==nil then + return + end + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Authentify key + local _validkey=self:_MarkerKeyAuthentification(Event.text) + + -- Check if we have the right coalition. + if _validkey then + + -- This should be the unique name of the target or move. + if _cancelmove then + if self.currentMove and self.currentMove.name==_name then + self.Controllable:ClearTasks() + self:Arrived() + else + self:RemoveMove(_name) + end + elseif _canceltarget then + if self.currentTarget and self.currentTarget.name==_name then + self:CeaseFire(self.currentTarget) + self:RemoveTarget(_name) + else + self:RemoveTarget(_name) + end + end + + end + end + end +end + +--- Function called when a F10 map mark was changed. This happens when a user enters text. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkChange(Event) + + -- Check if marker has a text and the "arty" keyword. + if Event.text~=nil and Event.text:lower():find("arty") then + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + local batteryname=self.Controllable:GetName() + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Evaluate marker text and extract parameters. + local _assign=self:_Markertext(Event.text) + + -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. + local _assigned=true + if #_assign.battery>0 then + _assigned=false + for _,bat in pairs(_assign.battery) do + self:T2(ARTY.id..string.format("Compare battery names %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) + if batteryname==bat then + _assigned=true + end + end + end + + -- We were not addressed. + if not _assigned then + return + end + + -- Check if ENGAGE or MOVE or REQUEST keywords were found. + if not (_assign.engage or _assign.move or _assign.request) then + return + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=self:_MarkerKeyAuthentification(Event.text) + + -- Handle requests and return. + if _assign.request and _validkey then + if _assign.requestammo then + self:_MarkRequestAmmo() + end + -- Done! + return + end + + -- Cancel current target and return. + if _assign.cancelcurrent and _validkey then + if _assign.move and self.currentMove then + self.Controllable:ClearTasks() + self:Arrived() + end + if _assign.engage and self.currentTarget then + self:CeaseFire(self.currentTarget) + end + return + end + + -- Handle engagements and relocations. + if _validkey then + + -- Convert (wrong x-->z, z-->x) vec3 + local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} + + -- Get coordinate from vec3. + local _coord=COORDINATE:NewFromVec3(vec3) + + -- Remove old mark because it might contain confidential data such as the key. + -- Also I don't know who can see the mark which was created. + _coord:RemoveMark(Event.idx) + + -- Anticipate marker ID. + -- WARNING: Make sure, no marks are set until the COORDINATE:MarkToCoalition() is called or the target/move name will be wrong and target cannot be removed by deleting its marker. + local _id=UTILS._MarkID+1 + + if _assign.move then + + -- Create a new name. This determins the string we search when deleting a move! + local _name=self:_MarkMoveName(_id) + + local text=string.format("%s, received new relocation assignment.", batteryname) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a relocation of the arty group. + local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) + + if _movename~=nil then + local _mid=self:_GetMoveIndexByName(_movename) + local _move=self.moves[_mid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_move.time)) + local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.", clock, _move.speed, tostring(_move.onroad)) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(100) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + else + local text=string.format("%s, relocation not possible.", batteryname) + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + else + + -- Create a new name. + local _name=self:_MarkTargetName(_id) + + local text=string.format("%s, received new target assignment.", batteryname) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + if _assign.time then + text=text..string.format("\nTime %s",_assign.time) + end + if _assign.prio then + text=text..string.format("\nPrio %d",_assign.prio) + end + if _assign.prio then + text=text..string.format("\nRadius %d m",_assign.radius) + end + if _assign.nshells then + text=text..string.format("\nShots %d",_assign.nshells) + end + if _assign.maxengage then + text=text..string.format("\nEngagements %d",_assign.maxengage) + end + if _assign.weapontype then + text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) + end + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a new firing engagement. + -- Note, we set unique=true so this target gets only added once. + local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) + + if _targetname~=nil then + local _tid=self:_GetTargetIndexByName(_targetname) + local _target=self.targets[_tid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_target.time)) + local weapon=self:_WeaponTypeName(_target.weapontype) + local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(250) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + end + end + end + + end + end + +end + --- Event handler for event Dead. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData @@ -2017,7 +1706,6 @@ function ARTY:onafterStatus(Controllable, From, Event, To) -- Get a commaned move to another location. local _move=self:_CheckMoves() - if (self:is("CombatReady") or self:is("Firing")) and _move then -- Group is combat ready or firing but we have a move. @@ -2049,7 +1737,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) end end - + -- Call status again in ~10 sec. self:__Status(self.StatusInterval) end @@ -2193,11 +1881,11 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) self:T(ARTY.id..text) 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 + --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. self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype) @@ -2226,7 +1914,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) -- Get target array index. local id=self:_GetTargetIndexByName(target.name) - -- Increase engaged counter + -- We have a target. if id then -- Target was actually engaged. (Could happen that engagement was aborted while group was still aiming.) if self.Nshots>0 then @@ -2270,7 +1958,7 @@ function ARTY:onafterWinchester(Controllable, From, Event, To) -- Send message. local text=string.format("%s, winchester!", Controllable:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 30):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) end @@ -2522,8 +2210,8 @@ function ARTY:onafterArrived(Controllable, From, Event, To) -- Set alarm state to auto. self.Controllable:OptionAlarmStateAuto() - -- Clear Tasks - self.Controllable:ClearTasks() + -- WARNING: calling ClearTasks() here causes CTD of DCS when move is over. Dont know why? combotask? + --self.Controllable:ClearTasks() -- Send message local text=string.format("%s, arrived at destination.", Controllable:GetName()) @@ -2532,10 +2220,10 @@ function ARTY:onafterArrived(Controllable, From, Event, To) -- Remove executed move from queue. if self.currentMove then - --self:RemoveMove(self.currentMove.name) - --self.currentMove=nil + self:RemoveMove(self.currentMove.name) + self.currentMove=nil end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2553,7 +2241,7 @@ function ARTY:onafterNewTarget(Controllable, From, Event, To, target) -- Debug message. local text=string.format("Adding new target %s.", target.name) - --MESSAGE:New(text, 30):ToAllIf(self.Debug) + MESSAGE:New(text, 5):ToAllIf(self.Debug) self:T(ARTY.id..text) end @@ -2570,7 +2258,7 @@ function ARTY:onafterNewMove(Controllable, From, Event, To, move) -- Debug message. local text=string.format("Adding new move %s.", move.name) - MESSAGE:New(text, 30):ToAllIf(self.Debug) + MESSAGE:New(text, 5):ToAllIf(self.Debug) self:T(ARTY.id..text) end @@ -2596,7 +2284,7 @@ function ARTY:onafterDead(Controllable, From, Event, To) -- Message. local text=string.format("%s, one of our units just died! %d units left.", self.Controllable:GetName(), nunits) - MESSAGE:New(text, 10):ToAllIf(self.Debug) + MESSAGE:New(text, 5):ToAllIf(self.Debug) self:T(ARTY.id..text) -- Go to stop state. @@ -2665,6 +2353,217 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype) group:SetTask(fire) end +--- 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 + +--- Route group to a certain point. +-- @param #ARTY self +-- @param Wrapper.Group#GROUP group Group to route. +-- @param Core.Point#COORDINATE ToCoord Coordinate where we want to go. +-- @param #number Speed Speed in km/h. +-- @param #boolean OnRoad If true, use (mainly) roads. +function ARTY:_Move(group, ToCoord, Speed, OnRoad) + + -- Clear all tasks. + group:ClearTasks() + group:OptionAlarmStateGreen() + group:OptionROEHoldFire() + + -- Set formation. + local formation = "Off Road" + + -- Default speed is 30 km/h. + Speed=Speed or 30 + + -- Current coordinates of group. + local cpini=group:GetCoordinate() + + -- Distance between current and final point. + local dist=cpini:Get2DDistance(ToCoord) + + -- Waypoint and task arrays. + local path={} + local task={} + + -- First waypoint is the current position of the group. + path[#path+1]=cpini:WaypointGround(Speed, formation) + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + -- Route group on road if requested. + if OnRoad then + + -- Path on road (only first and last points) + local _first=cpini:GetClosestPointToRoad() + local _last=ToCoord:GetClosestPointToRoad() + + -- First point on road. + path[#path+1]=_first:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + -- Last point on road. + path[#path+1]=_last:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + end + + -- Last waypoint at ToCoord. + path[#path+1]=ToCoord:WaypointGround(Speed, formation) + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) + + + -- Init waypoints of the group. + local Waypoints={} + + -- New points are added to the default route. + for i=1,#path do + table.insert(Waypoints, i, path[i]) + end + + -- Set task for all waypoints. + for i=1,#Waypoints do + group:SetTaskWaypoint(Waypoints[i], task[i]) + end + + -- Submit task and route group along waypoints. + group:Route(Waypoints) + +end + +--- Function called when group is passing a waypoint. +-- @param Wrapper.Group#GROUP group Group for which waypoint passing should be monitored. +-- @param #ARTY arty ARTY object. +-- @param #number i Waypoint number that has been reached. +-- @param #boolean final True if it is the final waypoint. +function ARTY._PassingWaypoint(group, arty, i, final) + + -- Debug message. + local text=string.format("%s, passing waypoint %d.", group:GetName(), i) + if final then + text=string.format("%s, arrived at destination.", group:GetName()) + end + arty:T(ARTY.id..text) + + --[[ + if final then + MESSAGE:New(text, 10):ToCoalitionIf(group:GetCoalition(), arty.Debug or arty.report) + else + MESSAGE:New(text, 10):ToAllIf(arty.Debug) + end + ]] + + -- Arrived event. + if final and arty.Controllable:GetName()==group:GetName() then + arty:Arrived() + end + +end + --- Relocate to another position, e.g. after an engagement to avoid couter strikes. -- @param #ARTY self function ARTY:_Relocate() @@ -2695,155 +2594,6 @@ function ARTY:_Relocate() end end ---- Sort targets with respect to priority and number of times it was already engaged. --- @param #ARTY self -function ARTY:_SortTargetQueuePrio() - self:F2() - - -- Sort results table wrt times they have already been engaged. - local function _sort(a, b) - return (a.engaged < b.engaged) or (a.engaged==b.engaged and a.prio < b.prio) - end - table.sort(self.targets, _sort) - - -- Debug output. - self:T3(ARTY.id.."Sorted targets wrt prio and number of engagements:") - for i=1,#self.targets do - local _target=self.targets[i] - self:T3(ARTY.id..string.format("Target %s", self:_TargetInfo(_target))) - end -end - ---- Sort array with respect to time. Array elements must have a .time entry. --- @param #ARTY self --- @param #table queue Array to sort. Should have elemnt .time. -function ARTY:_SortQueueTime(queue) - self:F3({queue=queue}) - - -- Sort targets w.r.t attack time. - local function _sort(a, b) - if a.time == nil and b.time == nil then - return false - end - if a.time == nil then - return false - end - if b.time == nil then - return true - end - return a.time < b.time - end - table.sort(queue, _sort) - - -- Debug output. - self:T3(ARTY.id.."Sorted queue wrt time:") - for i=1,#queue do - local _queue=queue[i] - local _time=tostring(_queue.time) - local _clock=tostring(self:_SecondsToClock(_queue.time)) - self:T3(ARTY.id..string.format("%s: time=%s, clock=%s", _queue.name, _time, _clock)) - end - -end - ---- Check all timed targets and return the target which should be attacked next. --- @param #ARTY self --- @return #table Target which is due to be attacked now. -function ARTY:_CheckTimedTargets() - self:F3() - - -- Current time. - local Tnow=timer.getAbsTime() - - -- Sort Targets wrt time. - self:_SortQueueTime(self.targets) - - for i=1,#self.targets do - local _target=self.targets[i] - - -- Debug info. - self:T3(ARTY.id..string.format("Check TIMED target %d: %s", i, self:_TargetInfo(_target))) - - -- Check if target has an attack time which has already passed. Also check that target is not under fire already and that it is in range. - if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target) then - - -- Check if group currently has a target and whether its priorty is lower than the timed target. - if self.currentTarget then - if self.currentTarget.prio > _target.prio then - -- Current target under attack but has lower priority than this target. - self:T2(ARTY.id..string.format("Found TIMED HIGH PRIO target %s.", self:_TargetInfo(_target))) - return _target - end - else - -- No current target. - self:T2(ARTY.id..string.format("Found TIMED target %s.", self:_TargetInfo(_target))) - return _target - end - end - end - - return nil -end - ---- Check all moves and return the one which should be executed next. --- @param #ARTY self --- @return #table Move which is due. -function ARTY:_CheckMoves() - self:F3() - - -- Current time. - local Tnow=timer.getAbsTime() - - -- Sort Targets wrt time. - self:_SortQueueTime(self.moves) - - -- Check if we are currently firing. - local firing=false - if self.currentTarget then - firing=true - end - - for i=1,#self.moves do - local _move=self.moves[i] - - -- Check if time for move is reached. - if Tnow >= _move.time and (firing==false or _move.cancel) then - return _move - end - end - - return nil -end - ---- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. --- @param #ARTY self --- @return #table Target which is due to be attacked now or nil if no target could be found. -function ARTY:_CheckNormalTargets() - self:F3() - - -- Sort targets w.r.t. prio and number times engaged already. - self:_SortTargetQueuePrio() - - -- Loop over all sorted targets. - for i=1,#self.targets do - local _target=self.targets[i] - - -- Debug info. - self:T3(ARTY.id..string.format("Check NORMAL target %d: %s", i, self:_TargetInfo(_target))) - - -- Check that target no time, is not under fire currently and in range. - if _target.underfire==false and _target.time==nil and _target.maxengage > _target.engaged and self:_TargetInRange(_target) then - - -- Debug info. - self:T2(ARTY.id..string.format("Found NORMAL target %s", self:_TargetInfo(_target))) - - return _target - end - end - - return nil -end - --- Get the number of shells a unit or group currently has. For a group the ammo count of all units is summed up. -- @param #ARTY self -- @param #boolean display Display ammo table as message to all. Default false. @@ -3007,6 +2757,405 @@ function ARTY:GetAmmo(display) return nammo, nshells, nrockets, nmissiles end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Mark Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text. +-- @return #boolean If true, authentification successful. +function ARTY:_MarkerKeyAuthentification(text) + + -- Set battery and coalition. + local batteryname=self.Controllable:GetName() + local batterycoalition=self.Controllable:GetCoalition() + + -- Get assignment. + local mykey=nil + if self.markkey~=nil then + + -- keywords are split by "," + local keywords=self:_split(text, ",") + for _,key in pairs(keywords) do + local s=self:_split(key, " ") + local val=s[2] + if key:lower():find("key") then + mykey=tonumber(val) + self:T(ARTY.id..string.format("Authorisation Key=%s.", val)) + end + end + + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=true + + -- Check if group needs authorization. + if self.markkey~=nil then + -- Assume key is incorrect. + _validkey=false + + -- If key was found, check if matches. + if mykey~=nil then + _validkey=self.markkey==mykey + end + self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", batteryname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) + + -- Send message + local text="" + if mykey==nil then + text=string.format("%s, authorization required but did not receive a key!", batteryname) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", batteryname, tostring(mykey)) + elseif _validkey==true then + text=string.format("%s, authentification successful!", batteryname) + end + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + return _validkey +end + +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text to be analyzed. +-- @return #table Table with assignment parameters, e.g. number of shots, radius, time etc. +function ARTY:_Markertext(text) + self:F(text) + + -- Assignment parameters. + local assignment={} + assignment.battery={} + assignment.move=false + assignment.engage=false + assignment.request=false + assignment.readonly=false + assignment.cancelcurrent=false + --assignment.time=nil + --assignment.nshells=nil + --assignment.prio=nil + --assignment.maxengage=nil + --assignment.radius=nil + --assignment.weapontype=nil + --assignment.speed=nil + --assignment.onroad=nil + --assignment.key=nil + + if text:lower():find("arty") then + if text:lower():find("engage") then + assignment.engage=true + elseif text:lower():find("move") then + assignment.move=true + elseif text:lower():find("request") then + assignment.request=true + else + self:E(ARTY.id.."ERROR: Neither ENGAGE nor MOVE keyword specified!") + return + end + + -- keywords are split by "," + local keywords=self:_split(text, ",") + + for _,key in pairs(keywords) do + + local s=self:_split(key, " ") + local val=s[2] + + -- Battery name, i.e. which ARTY group should fire. + if key:lower():find("battery") then + + local v=self:_split(key, '"') + + for i=2,#v,2 do + table.insert(assignment.battery, v[i]) + self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) + end + + elseif key:lower():find("time") then + + if val:lower():find("now") then + assignment.time=self:_SecondsToClock(timer.getTime0()+5) + else + assignment.time=val + end + self:T2(ARTY.id..string.format("Key Time=%s.", val)) + + elseif key:lower():find("shots") then + + assignment.nshells=tonumber(s[2]) + self:T(ARTY.id..string.format("Key Shots=%s.", val)) + + elseif key:lower():find("prio") then + + assignment.prio=tonumber(val) + self:T2(string.format("Key Prio=%s.", val)) + + elseif key:lower():find("maxengage") then + + assignment.maxengage=tonumber(val) + self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) + + elseif key:lower():find("radius") then + + assignment.radius=tonumber(val) + self:T2(ARTY.id..string.format("Key Radius=%s.", val)) + + elseif key:lower():find("weapon") then + + if val:lower():find("cannon") then + assignment.weapontype=ARTY.WeaponType.Cannon + elseif val:lower():find("rocket") then + assignment.weapontype=ARTY.WeaponType.Rockets + elseif val:lower():find("missile") then + assignment.weapontype=ARTY.WeaponType.GuidedMissile + elseif val:lower():find("nuke") then + assignment.weapontype=ARTY.WeaponType.TacticalNukes + else + assignment.weapontype=ARTY.WeaponType.Auto + end + self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) + + elseif key:lower():find("speed") then + + assignment.speed=tonumber(val) + self:T2(ARTY.id..string.format("Key Speed=%s.", val)) + + elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then + + assignment.onroad=true + self:T2(ARTY.id..string.format("Key Onroad=true.")) + + elseif key:lower():find("irrevocable") or key:lower():find("readonly") then + assignment.readonly=true + self:T2(ARTY.id..string.format("Key Readonly=true.")) + + elseif key:lower():find("cancel current") then + assignment.cancelcurrent=true + self:T2(ARTY.id..string.format("Key Cancel Current=true.")) + elseif assignment.request and key:lower():find("ammo") then + assignment.requestammo=true + end + + end + else + self:T2(ARTY.id..string.format("This is NO arty command:\n%s", tostring(text))) + end + + return assignment +end + +--- Request ammo. +-- @param #ARTY self +function ARTY:_MarkRequestAmmo() + self:GetAmmo(true) +end + +--- Create a name for an engagement initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of target engagement. +function ARTY:_MarkTargetName(markerid) + return string.format("BATTERY=%s, Marked Target ID=%d", self.Controllable:GetName(), markerid) +end + +--- Create a name for a relocation move initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of relocation move. +function ARTY:_MarkMoveName(markerid) + return string.format("BATTERY=%s, Marked Relocation ID=%d", self.Controllable:GetName(), markerid) +end + +--- Create a name for a relocation move initiated by placing a marker. +-- @param #ARTY self +-- @param #string name Name of the assignment. +-- @return #string Name of the ARTY group or nil +-- @return #number ID of the marked target or nil. +-- @return #number ID of the marked relocation move or nil +function ARTY:_GetMarkIDfromName(name) + + -- keywords are split by "," + local keywords=self:_split(name, ",") + + local battery=nil + local markTID=nil + local markMID=nil + + for _,key in pairs(keywords) do + + local str=self:_split(key, "=") + local par=str[1] + local val=str[2] + + if par:find("BATTERY") then + battery=val + end + if par:find("Marked Target ID") then + markTID=tonumber(val) + end + if par:find("Marked Relocation ID") then + markMID=tonumber(val) + end + + end + + return battery, markTID, markMID +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Helper Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Sort targets with respect to priority and number of times it was already engaged. +-- @param #ARTY self +function ARTY:_SortTargetQueuePrio() + self:F2() + + -- Sort results table wrt times they have already been engaged. + local function _sort(a, b) + return (a.engaged < b.engaged) or (a.engaged==b.engaged and a.prio < b.prio) + end + table.sort(self.targets, _sort) + + -- Debug output. + self:T3(ARTY.id.."Sorted targets wrt prio and number of engagements:") + for i=1,#self.targets do + local _target=self.targets[i] + self:T3(ARTY.id..string.format("Target %s", self:_TargetInfo(_target))) + end +end + +--- Sort array with respect to time. Array elements must have a .time entry. +-- @param #ARTY self +-- @param #table queue Array to sort. Should have elemnt .time. +function ARTY:_SortQueueTime(queue) + self:F3({queue=queue}) + + -- Sort targets w.r.t attack time. + local function _sort(a, b) + if a.time == nil and b.time == nil then + return false + end + if a.time == nil then + return false + end + if b.time == nil then + return true + end + return a.time < b.time + end + table.sort(queue, _sort) + + -- Debug output. + self:T3(ARTY.id.."Sorted queue wrt time:") + for i=1,#queue do + local _queue=queue[i] + local _time=tostring(_queue.time) + local _clock=tostring(self:_SecondsToClock(_queue.time)) + self:T3(ARTY.id..string.format("%s: time=%s, clock=%s", _queue.name, _time, _clock)) + end + +end + +--- Check all timed targets and return the target which should be attacked next. +-- @param #ARTY self +-- @return #table Target which is due to be attacked now. +function ARTY:_CheckTimedTargets() + self:F3() + + -- Current time. + local Tnow=timer.getAbsTime() + + -- Sort Targets wrt time. + self:_SortQueueTime(self.targets) + + for i=1,#self.targets do + local _target=self.targets[i] + + -- Debug info. + self:T3(ARTY.id..string.format("Check TIMED target %d: %s", i, self:_TargetInfo(_target))) + + -- Check if target has an attack time which has already passed. Also check that target is not under fire already and that it is in range. + if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target) then + + -- Check if group currently has a target and whether its priorty is lower than the timed target. + if self.currentTarget then + if self.currentTarget.prio > _target.prio then + -- Current target under attack but has lower priority than this target. + self:T2(ARTY.id..string.format("Found TIMED HIGH PRIO target %s.", self:_TargetInfo(_target))) + return _target + end + else + -- No current target. + self:T2(ARTY.id..string.format("Found TIMED target %s.", self:_TargetInfo(_target))) + return _target + end + end + end + + return nil +end + +--- Check all moves and return the one which should be executed next. +-- @param #ARTY self +-- @return #table Move which is due. +function ARTY:_CheckMoves() + self:F3() + + -- Current time. + local Tnow=timer.getAbsTime() + + -- Sort Targets wrt time. + self:_SortQueueTime(self.moves) + + -- Check if we are currently firing. + local firing=false + if self.currentTarget then + firing=true + end + + for i=1,#self.moves do + local _move=self.moves[i] + + -- Check if time for move is reached. + if Tnow >= _move.time and (firing==false or _move.cancel) then + return _move + end + end + + return nil +end + +--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. +-- @param #ARTY self +-- @return #table Target which is due to be attacked now or nil if no target could be found. +function ARTY:_CheckNormalTargets() + self:F3() + + -- Sort targets w.r.t. prio and number times engaged already. + self:_SortTargetQueuePrio() + + -- Loop over all sorted targets. + for i=1,#self.targets do + local _target=self.targets[i] + + -- Debug info. + self:T3(ARTY.id..string.format("Check NORMAL target %d: %s", i, self:_TargetInfo(_target))) + + -- Check that target no time, is not under fire currently and in range. + if _target.underfire==false and _target.time==nil and _target.maxengage > _target.engaged and self:_TargetInRange(_target) then + + -- Debug info. + self:T2(ARTY.id..string.format("Found NORMAL target %s", self:_TargetInfo(_target))) + + return _target + end + end + + return nil +end --- Check whether shooting started within a certain time (~5 min). If not, the current target is considered invalid and removed from the target list. -- @param #ARTY self @@ -3084,8 +3233,6 @@ function ARTY:_GetMoveIndexByName(name) return nil end - - --- Check if a name is unique. If not, a new unique name can be created by adding a running index #01, #02, ... -- @param #ARTY self -- @param #table givennames Table with entries of already given names. Must contain a .name item. @@ -3178,7 +3325,7 @@ function ARTY:_TargetInRange(target) -- Debug output. if not _inrange then self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 5):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) end -- Remove target if ARTY group cannot move. No change to be ever in range. @@ -3355,106 +3502,7 @@ function ARTY:_ClockToSeconds(clock) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Route group to a certain point. --- @param #ARTY self --- @param Wrapper.Group#GROUP group Group to route. --- @param Core.Point#COORDINATE ToCoord Coordinate where we want to go. --- @param #number Speed Speed in km/h. --- @param #boolean OnRoad If true, use (mainly) roads. -function ARTY:_Move(group, ToCoord, Speed, OnRoad) - - -- Clear all tasks. - group:ClearTasks() - group:OptionAlarmStateGreen() - group:OptionROEHoldFire() - - -- Set formation. - local formation = "Off road" - - -- Default speed is 30 km/h. - Speed=Speed or 30 - - -- Current coordinates of group. - local cpini=group:GetCoordinate() - - -- Distance between current and final point. - local dist=cpini:Get2DDistance(ToCoord) - - -- Waypoint and task arrays. - local path={} - local task={} - - -- First waypoint is the current position of the group. - path[#path+1]=cpini:WaypointGround(Speed, formation) - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) - - -- Route group on road if requested. - if OnRoad then - - -- Path on road (only first and last points) - local _first=cpini:GetClosestPointToRoad() - local _last=ToCoord:GetClosestPointToRoad() - - -- First point on road. - path[#path+1]=_first:WaypointGround(Speed, "On Road") - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) - - -- Last point on road. - path[#path+1]=_last:WaypointGround(Speed, "On Road") - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) - - end - - -- Last waypoint at ToCoord. - path[#path+1]=ToCoord:WaypointGround(Speed, formation) - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) - - - -- Init waypoints of the group. - local Waypoints={} - - -- New points are added to the default route. - for i=1,#path do - table.insert(Waypoints, i, path[i]) - end - - -- Set task for all waypoints. - for i=1,#Waypoints do - group:SetTaskWaypoint(Waypoints[i], task[i]) - end - - -- Submit task and route group along waypoints. - group:Route(Waypoints) - -end - ---- Function called when group is passing a waypoint. --- @param Wrapper.Group#GROUP group Group for which waypoint passing should be monitored. --- @param #ARTY arty ARTY object. --- @param #number i Waypoint number that has been reached. --- @param #boolean final True if it is the final waypoint. -function ARTY._PassingWaypoint(group, arty, i, final) - - -- Debug message. - local text=string.format("%s, passing waypoint %d.", group:GetName(), i) - if final then - text=string.format("%s, arrived at destination.", group:GetName()) - end - arty:T(ARTY.id..text) - - --[[ - if final then - MESSAGE:New(text, 10):ToCoalitionIf(group:GetCoalition(), arty.Debug or arty.report) - else - MESSAGE:New(text, 10):ToAllIf(arty.Debug) - end - ]] - - -- Arrived event. - if final and arty.Controllable:GetName()==group:GetName() then - arty:Arrived() - end - -end \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5e7a15ef9..c438e751c 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -355,9 +355,9 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local function SetTask( Controller, DCSTask ) if self and self:IsAlive() then local Controller = self:_GetController() - self:I( "Before SetTask" ) + --self:I( "Before SetTask" ) Controller:setTask( DCSTask ) - self:I( "After SetTask" ) + --self:I( "After SetTask" ) else BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) end From bf7523fa8530c09a35fd3a4bffaea4065beecdce Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 3 Jun 2018 01:00:16 +0200 Subject: [PATCH 07/16] messages clearsceen --- Moose Development/Moose/Core/Message.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index e11f807ea..f496796a1 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -317,7 +317,7 @@ function MESSAGE:ToAll() if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end return self From 0a34cfdafae50c17cb77807e36499339e961f566 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 3 Jun 2018 23:38:15 +0200 Subject: [PATCH 08/16] ARTY v0.9.91 - Added automatic relocation if not in firing range. - Added first version of ARTY DB with artillery unit parameters min/max firing range. --- Moose Development/Moose/Core/Message.lua | 11 +- .../Moose/Functional/Artillery.lua | 269 ++++++++++++++++-- .../Moose/Functional/Suppression.lua | 2 +- 3 files changed, 264 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 4ca1d4bc3..b50f52ddb 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -66,7 +66,7 @@ MESSAGE.Type = { -- @param #string MessageText is the text of the Message. -- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. -- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @param #boolean ClearScreen (optional) Clear all previous messages. +-- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @return #MESSAGE -- @usage -- -- Create a series of new Messages. @@ -147,6 +147,15 @@ end +--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. +-- @param #MESSAGE self +-- @return #MESSAGE +function MESSAGE:Clear() + self:F() + self.ClearScreen=true + return self +end + --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index ba42d1c14..70a513e2e 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -88,6 +88,9 @@ -- @field #boolean markallow If true, Players are allowed to assign targets and moves for ARTY group by placing markers on the F10 map. Default is false. -- @field #number markkey Authorization key. Only player who know this key can assign targets and moves via markers on the F10 map. Default no authorization required. -- @field #boolean markreadonly Marks for targets are readonly and cannot be removed by players. Default is false. +-- @field #boolean autorelocate ARTY group will automatically move to within the max/min firing range. +-- @field #number autorelocatemaxdist Max distance [m] the ARTY group will travel to get within firing range. Default 50000 m = 50 km. +-- @field #boolean autorelocateonroad ARTY group will use mainly road to automatically get within firing range. Default is false. -- @extends Core.Fsm#FSM_CONTROLLABLE --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can @@ -218,6 +221,7 @@ -- * @{#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}.TacticalNukes: Use tactical nuclear shells. This works only with units that have shells and is described below. -- -- ## Assigning Moves -- The ARTY group can be commanded to move. This is done by the @{#ARTY.AssignMoveCoord}(*coord*,*time*,*speed*,*onroad*,*cancel*,*name*) function. @@ -258,13 +262,15 @@ -- -- ## Tactical Nukes -- --- ARTY groups that can fire shells can also be used to fire tactical nukes. This is simply achieved by setting the weapon type to **ARTY.WeaponType.TacticalNukes** in the +-- ARTY groups that can fire shells can also be used to fire tactical nukes. This is achieved by setting the weapon type to **ARTY.WeaponType.TacticalNukes** in the -- @{#ARTY.AssignTargetCoord}() function. +-- +-- By default, they group does not have any nukes available. To give the group the ability the function @{#ARTY.SetTacNukeShells}(*n*) can be used. +-- This supplies the group with *n* nuclear shells, where *n* is restricted to the number of conventional shells the group can carry. +-- Note that the group must always have convenctional shells left in order to fire a nuclear shell. -- -- The default explostion strength is 0.075 kilo tons TNT. The can be changed with the @{#ARTY.SetTacNukeWarhead}(*strength*), where *strength* is given in kilo tons TNT. --- --- By default, all available conventional shells can be used as nuclear shells. However, it is possible to restrict the number with the @{#ARTY.SetTacNukeShells}(*n*) function --- to only have *n* nuclear shells available. Note that the group must always have convenctional shells left in order to fire a nuclear shell. +-- -- -- ## Assignments via Markers on F10 Map -- @@ -438,6 +444,9 @@ ARTY={ markallow=false, markkey=nil, markreadonly=false, + autorelocate=false, + autorelocatemaxdist=50000, + autorelocateonroad=false, } --- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag @@ -453,13 +462,54 @@ ARTY.WeaponType={ TacticalNukes=666, } +--- Database of common artillery unit properties. +-- @list db +ARTY.db={ + ["2B11 mortar"] = { + minrange = 500, + maxrange = 7000, + }, + ["SAU 2-C9"] = { + minrange = 500, + maxrange = 7000, + }, + ["SPH M109 Paladin"] = { + minrange = 300, + maxrange = 22000, + }, + ["SAU Gvozdika"] = { + minrange = 300, + maxrange = 15000, + }, + ["SAU Akatsia"] = { + minrange = 300, + maxrange = 17000, + }, + ["SAU Msta"] = { + minrange = 300, + maxrange = 23500, + }, + ["MLRS M270"] = { + minrange = 10000, + maxrange = 32000, + }, + ["Grad-URAL"] = { + minrange = 5000, + maxrange = 19000, + }, + ["Smerch"] = { + minrange = 20000, + maxrange = 70000, + } +} + --- Some ID to identify who we are in output of the DCS.log file. -- @field #string id ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.9" +ARTY.version="0.9.91" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -826,6 +876,21 @@ function ARTY:SetRearmingPlace(coord) self.RearmingPlaceCoord=coord end +--- Set automatic relocation of ARTY group if a target is assigned which is out of range. The unit will drive automatically towards or away from the target to be in max/min firing range. +-- @param #ARTY self +-- @param #number maxdistance (Optional) The maximum distance in km the group will travel to get within firing range. Default is 50 km. No automatic relocation is performed if targets are assigned which are further away. +-- @param #boolean onroad (Optional) If true, ARTY group uses roads whenever possible. Default false, i.e. group will move in a straight line to the assigned coordinate. +function ARTY:SetAutomaticRelocate(maxdistance, onroad) + self:F({distance=maxdistance, onroad=onroad}) + self.autorelocate=true + self.autorelocatemaxdist=maxdistance or 50 + self.autorelocatemaxdist=self.autorelocatemaxdist*1000 + if onroad==nil then + onroad=false + end + self.autorelocateonroad=onroad +end + --- Report messages of ARTY group turned on. This is the default. -- @param #ARTY self function ARTY:SetReportON() @@ -1045,6 +1110,18 @@ function ARTY:onafterStart(Controllable, From, Event, To) self.Nukes0=0 end + -- Check if we have and arty type that is in the DB. + local _dbproperties=self:_CheckDB(self.DisplayName) + self:T({dbproperties=_dbproperties}) + if _dbproperties~=nil then + for property,value in pairs(_dbproperties) do + self:T({property=property, value=value}) + --if self[property]==nil then + self[property]=value + --end + end + end + local text=string.format("\n******************************************************\n") text=text..string.format("Arty group = %s\n", Controllable:GetName()) text=text..string.format("Artillery attribute = %s\n", tostring(self.IsArtillery)) @@ -1079,6 +1156,9 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Relocate after fire = %s\n", tostring(self.relocateafterfire)) text=text..string.format("Relocate min dist. = %d m\n", self.relocateRmin) text=text..string.format("Relocate max dist. = %d m\n", self.relocateRmax) + text=text..string.format("Auto move in range = %s\n", tostring(self.autorelocate)) + text=text..string.format("Auto move dist. max = %.1f km\n", self.autorelocatemaxdist/1000) + text=text..string.format("Auto move on road = %s\n", tostring(self.autorelocateonroad)) text=text..string.format("Marker assignments = %s\n", tostring(self.markallow)) text=text..string.format("Marker auth. key = %s\n", tostring(self.markkey)) text=text..string.format("Marker readonly = %s\n", tostring(self.markreadonly)) @@ -1129,6 +1209,20 @@ function ARTY:onafterStart(Controllable, From, Event, To) self:__Status(self.StatusInterval) end +--- Check the DB for properties of the specified artillery unit type. +-- @param #ARTY self +-- @return #table Properties of the requested artillery type. Returns nil if no matching DB entry could be found. +function ARTY:_CheckDB(displayname) + for _type,_properties in pairs(ARTY.db) do + self:T({type=_type, properties=_properties}) + if _type==displayname then + self:T({type=_type, properties=_properties}) + return _properties + end + end + return nil +end + --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self function ARTY:_StatusReport() @@ -1209,7 +1303,7 @@ function ARTY:_OnEventShot(EventData) -- 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) self:T(ARTY.id..text) - MESSAGE:New(text, 5):ToAllIf(self.report or self.Debug) + MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug) -- Last known position of the weapon fired. local _lastpos={x=0, y=0, z=0} @@ -1484,7 +1578,7 @@ function ARTY:_OnEventMarkChange(Event) end end end - + -- We were not addressed. if not _assigned then return @@ -1696,9 +1790,12 @@ function ARTY:onafterStatus(Controllable, From, Event, To) -- Check that firing started after ~5 min. If not, target is removed. self:_CheckShootingStarted() end + + + -- Check if targets are in range and update target.inrange value. + self:_CheckTargetsInRange() - - -- Get a valid timed target if it is due to be attacked. + -- Get a valid timed target if it is due to be attacked. local _timedTarget=self:_CheckTimedTargets() -- Get a valid normal target (one that is not timed). @@ -2239,6 +2336,9 @@ end function ARTY:onafterNewTarget(Controllable, From, Event, To, target) self:_EventFromTo("onafterNewTarget", Event, From, To) + -- Check if target is in range. + --local _inrange, _toofar, _tooclose=self:_TargetInRange(target, self.report or self.Debug) + -- Debug message. local text=string.format("Adding new target %s.", target.name) MESSAGE:New(text, 5):ToAllIf(self.Debug) @@ -2831,6 +2931,7 @@ function ARTY:_Markertext(text) assignment.engage=false assignment.request=false assignment.readonly=false + assignment.canceltarget=false assignment.cancelcurrent=false --assignment.time=nil --assignment.nshells=nil @@ -2881,10 +2982,10 @@ function ARTY:_Markertext(text) end self:T2(ARTY.id..string.format("Key Time=%s.", val)) - elseif key:lower():find("shots") then + elseif key:lower():find("shot") then assignment.nshells=tonumber(s[2]) - self:T(ARTY.id..string.format("Key Shots=%s.", val)) + self:T(ARTY.id..string.format("Key Shot=%s.", val)) elseif key:lower():find("prio") then @@ -2927,12 +3028,20 @@ function ARTY:_Markertext(text) self:T2(ARTY.id..string.format("Key Onroad=true.")) elseif key:lower():find("irrevocable") or key:lower():find("readonly") then + assignment.readonly=true self:T2(ARTY.id..string.format("Key Readonly=true.")) + + elseif key:lower():find("canceltarget") then + + assignment.canceltarget=true + self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) - elseif key:lower():find("cancel current") then + elseif 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("ammo") then assignment.requestammo=true end @@ -3059,6 +3168,122 @@ function ARTY:_SortQueueTime(queue) end +--- Heading from point a to point b in degrees. +--@param #ARTY self +--@param Core.Point#COORDINATE a Coordinate. +--@param Core.Point#COORDINATE b Coordinate. +--@return #number angle Angle from a to b in degrees. +function ARTY:_GetHeading(a, b) + local dx = b.x-a.x + local dy = b.z-a.z + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + return angle +end + +--- Check all targets whether they are in range. +-- @param #ARTY self +function ARTY:_CheckTargetsInRange() + + for i=1,#self.targets do + local _target=self.targets[i] + + self:T(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange))) + + -- Check if target is in range. + 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))) + + -- Init default for assigning moves into range. + local _movetowards=false + local _moveaway=false + + if _target.inrange==nil then + + -- First time the check is performed. We call the function again and send a message. + _target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug) + + -- Send group towards/away from target. + if _toofar then + _movetowards=true + elseif _tooclose then + _moveaway=true + end + + elseif _target.inrange==true then + + -- Target was in range at previous check... + + if _toofar then --...but is now too far away. + _movetowards=true + elseif _tooclose then --...but is now too close. + _moveaway=true + end + + elseif _target.inrange==false then + + -- Target was out of range at previous check. + + if _inrange then + -- Inform coalition that target is now in range. + local text=string.format("%s, target %s is now in range.", self.Controllable:GetName(), _target.name) + self:T(ARTY.id..text) + MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + end + + end + + -- Assign a relocation command so that the unit will be in range of the requested target. + if self.autorelocate and (_movetowards or _moveaway) then + + -- Get current position. + local _from=self.Controllable:GetCoordinate() + local _dist=_from:Get2DDistance(_target.coord) + + if _dist<=self.autorelocatemaxdist then + + local _tocoord --Core.Point#COORDINATE + local _name="" + local _safetymargin=500 + + if _movetowards then + + -- Target was in range on previous check but now we are too far away. + local _waytogo=_dist-self.maxrange+_safetymargin + local _heading=self:_GetHeading(_from,_target.coord) + _tocoord=_from:Translate(_waytogo, _heading) + _name=string.format("Relocation to within max firing range of target %s", _target.name) + + elseif _moveaway then + + -- Target was in range on previous check but now we are too far away. + local _waytogo=_dist-self.minrange+_safetymargin + local _heading=self:_GetHeading(_target.coord,_from) + _tocoord=_from:Translate(_waytogo, _heading) + _name=string.format("Relocation to within min firing range of target %s", _target.name) + + end + + -- Send info message. + MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + + -- Assign relocation move. + self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) + + end + + end + + -- Update value. + _target.inrange=_inrange + + self:T(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange))) + + end +end + --- Check all timed targets and return the target which should be attacked next. -- @param #ARTY self -- @return #table Target which is due to be attacked now. @@ -3303,37 +3528,49 @@ end --- Check if target is in range. -- @param #ARTY self -- @param #table target Target table. +-- @param #boolean message (Optional) If true, send a message to the coalition if the target is not in range. Default is no message is send. -- @return #boolean True if target is in range, false otherwise. -function ARTY:_TargetInRange(target) +-- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range. +-- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range. +function ARTY:_TargetInRange(target, message) self:F3(target) + + -- Default is no message. + if message==nil then + message=false + end -- Distance between ARTY group and target. local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord) -- Assume we are in range. local _inrange=true + local _tooclose=false + local _toofar=false local text="" if _dist < self.minrange then _inrange=false + _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) elseif _dist > self.maxrange then _inrange=false + _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) end -- Debug output. if not _inrange then self:T(ARTY.id..text) - MESSAGE:New(text, 5):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 5):ToCoalitionIf(self.Controllable:GetCoalition(), (self.report and message) or (self.Debug and message)) end - -- Remove target if ARTY group cannot move. No change 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 self:RemoveTarget(target.name) end - return _inrange + return _inrange,_toofar,_tooclose end --- Get the weapon type name, which should be used to attack the target. diff --git a/Moose Development/Moose/Functional/Suppression.lua b/Moose Development/Moose/Functional/Suppression.lua index c0d2278ef..805008e45 100644 --- a/Moose Development/Moose/Functional/Suppression.lua +++ b/Moose Development/Moose/Functional/Suppression.lua @@ -1725,7 +1725,7 @@ end --@param Core.Point#COORDINATE a Coordinate. --@param Core.Point#COORDINATE b Coordinate. --@return #number angle Angle from a to b in degrees. -function SUPPRESSION:_Heading(a, b, distance) +function SUPPRESSION:_Heading(a, b) local dx = b.x-a.x local dy = b.z-a.z local angle = math.deg(math.atan2(dy,dx)) From 8b667071b7a76e34ac11f2c5bfe7f18625af543a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 5 Jun 2018 00:28:52 +0200 Subject: [PATCH 09/16] ARTY v0.9.92 - adjusted DB - other minor fixes and improvements --- .../Moose/Functional/Artillery.lua | 138 ++++++++++++------ .../Moose/Wrapper/Controllable.lua | 6 +- 2 files changed, 98 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 70a513e2e..5bec76c49 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -59,7 +59,7 @@ -- @field #string Type Type 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 #boolean IsArtillery If true, ARTY group has attribute "Artillery". +-- @field #boolean IsArtillery If true, ARTY group has attribute "Artillery". This is automatically derived 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 RearmingDistance Safe distance in meters between ARTY group and rearming group or place at which rearming is possible. Default 100 m. @@ -286,11 +286,12 @@ -- * *time* Time for which which the engagement is schedules, e.g. 08:42. Default is as soon as possible. -- * *prio* Priority of the engagement as number between 1 (high prio) and 100 (low prio). Default is 50. -- * *shots* Number of shots (shells, rockets or missiles) fired at each engagement. Default is 5. --- * *engage* Number of times the target is engaged. Default is 1. +-- * *maxengage* Number of times the target is engaged. Default is 1. -- * *radius* Scattering radius of the fired shots in meters. Default is 100 m. -- * *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. -- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. +-- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker. -- -- Here are examples of valid marker texts: -- arty engage! @@ -307,9 +308,11 @@ -- -- * *time* Time for which which the relocation/move is schedules, e.g. 08:42. Default is as soon as possible. -- * *speed* The speed in km/h the group will drive at. Default is 70% of its max possible speed. --- * *onroad* Group will use mainly roads. Default is off, i.e. it will go in a straight line from its current position to the assigned coordinate. --- * *cancel* Group will cancel all running firing engagements and immidiately start to move. Default is that group will wait until is current assignment is over. +-- * *on road* Group will use mainly roads. Default is off, i.e. it will go in a straight line from its current position to the assigned coordinate. +-- * *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. +-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. +-- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker. -- -- Here are some examples: -- arty move! time 23:45, speed 50, onroad, cancel @@ -431,7 +434,7 @@ ARTY={ ammorockets={}, ammomissiles={}, Nshots=0, - minrange=100, + minrange=300, maxrange=1000000, nukewarhead=75000, Nukes=nil, @@ -465,42 +468,61 @@ ARTY.WeaponType={ --- Database of common artillery unit properties. -- @list db ARTY.db={ - ["2B11 mortar"] = { - minrange = 500, - maxrange = 7000, + ["2B11 mortar"] = { -- type "2B11 mortar" + minrange = 500, -- correct? + maxrange = 7000, -- 7 km + reloadtime = 30, -- 30 sec }, - ["SAU 2-C9"] = { - minrange = 500, - maxrange = 7000, + ["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika" + minrange = 300, -- correct? + maxrange = 15000, -- 15 km + reloadtime = nil, -- unknown }, - ["SPH M109 Paladin"] = { - minrange = 300, - maxrange = 22000, + ["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta" + minrange = 300, -- correct? + maxrange = 23500, -- 23.5 km + reloadtime = nil, -- unknown }, - ["SAU Gvozdika"] = { - minrange = 300, - maxrange = 15000, + ["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia" + minrange = 300, -- correct? + maxrange = 17000, -- 17 km + reloadtime = nil, -- unknown }, - ["SAU Akatsia"] = { - minrange = 300, - maxrange = 17000, + ["SPH 2S9 Nona"] = { --type "SAU 2-C9" + minrange = 500, -- correct? + maxrange = 7000, -- 7 km + reloadtime = nil, -- unknown }, - ["SAU Msta"] = { - minrange = 300, - maxrange = 23500, + ["SPH M109 Paladin"] = { -- type "M-109", alias "M109" + minrange = 300, -- correct? + maxrange = 22000, -- 22 km + reloadtime = nil, -- unknown }, - ["MLRS M270"] = { - minrange = 10000, - maxrange = 32000, + ["SpGH Dana"] = { -- type "SpGH_Dana" + minrange = 300, -- correct? + maxrange = 18700, -- 18.7 km + reloadtime = nil, -- unknown }, - ["Grad-URAL"] = { - minrange = 5000, - maxrange = 19000, + ["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" + minrange = 5000, -- 5 km + maxrange = 19000, -- 19 km + reloadtime = 420, -- 7 min + }, + ["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" + minrange = 11500, -- 11.5 km + maxrange = 35800, -- 35.8 km + reloadtime = 840, -- 14 min + }, + ["MLRS 9A52 Smerch"] = { -- type "Smerch" + minrange = 20000, -- 20 km + maxrange = 70000, -- 70 km + reloadtime = 2160, -- 36 min + }, + ["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" + minrange = 10000, -- 10 km + maxrange = 32000, -- 32 km + reloadtime = 540, -- 9 min }, - ["Smerch"] = { - minrange = 20000, - maxrange = 70000, - } } --- Some ID to identify who we are in output of the DCS.log file. @@ -509,7 +531,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.91" +ARTY.version="0.9.92" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -759,8 +781,9 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) -- Get max speed of group. local speedmax=self.Controllable:GetSpeedMax() - -- Default speed is 70% of max speed. + -- Set speed. if speed then + -- Make sure, given speed is less than max phycially possible speed of group. speed=math.min(speed, speedmax) elseif self.Speed then speed=self.Speed @@ -1640,7 +1663,7 @@ function ARTY:_OnEventMarkChange(Event) MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) -- Assign a relocation of the arty group. - local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.cancel,_name, true) + local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.canceltarget,_name, true) if _movename~=nil then local _mid=self:_GetMoveIndexByName(_movename) @@ -2566,7 +2589,7 @@ end -- @param #ARTY self -- @param Wrapper.Group#GROUP group Group to route. -- @param Core.Point#COORDINATE ToCoord Coordinate where we want to go. --- @param #number Speed Speed in km/h. +-- @param #number Speed (Optional) Speed in km/h. Default is 70% of max speed the group can do. -- @param #boolean OnRoad If true, use (mainly) roads. function ARTY:_Move(group, ToCoord, Speed, OnRoad) @@ -2578,8 +2601,14 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) -- Set formation. local formation = "Off Road" - -- Default speed is 30 km/h. - Speed=Speed or 30 + -- Get max speed of group. + local SpeedMax=group:GetSpeedMax() + + -- Set speed. + Speed=Speed or SpeedMax*0.7 + + -- Make sure, we do not go above max speed possible. + Speed=math.min(Speed, SpeedMax) -- Current coordinates of group. local cpini=group:GetCoordinate() @@ -2757,11 +2786,30 @@ function ARTY:GetAmmo(display) -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 local Category=ammotable[w].desc.category + -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 local MissileCategory=nil if Category==Weapon.Category.MISSILE then MissileCategory=ammotable[w].desc.missileCategory end + local function missilecat(n) + local cat="unknown" + if n==1 then + cat="air-to-air" + elseif n==2 then + cat="surface-to-air" + elseif n==3 then + cat="ballistic" + elseif n==4 then + cat="anti-ship" + elseif n==5 then + cat="cruise" + elseif n==6 then + cat="other" + end + return cat + end + -- Check for correct shell type. local _gotshell=false if #self.ammoshells>0 then @@ -2812,7 +2860,7 @@ function ARTY:GetAmmo(display) nshells=nshells+Nammo -- Debug info. - text=text..string.format("- %d shells of type %s (category=%d, missile category=%s)\n", Nammo, Tammo, Category, tostring(MissileCategory)) + text=text..string.format("- %d shells of type %s\n", Nammo, Tammo) elseif _gotrocket then @@ -2820,15 +2868,17 @@ function ARTY:GetAmmo(display) nrockets=nrockets+Nammo -- Debug info. - text=text..string.format("- %d rockets of type %s (category=%d, missile category=%s)\n", Nammo, Tammo, Category, tostring(MissileCategory)) + text=text..string.format("- %d rockets of type %s\n", Nammo, Tammo) elseif _gotmissile then - -- Add up all rockets. - nmissiles=nmissiles+Nammo + -- Add up all cruise missiles (category 5) + if MissileCategory==5 then + nmissiles=nmissiles+Nammo + end -- Debug info. - text=text..string.format("- %d missiles of type %s (category=%d, missile category=%s)\n", Nammo, Tammo, Category, tostring(MissileCategory)) + text=text..string.format("- %d %s missiles of type %s\n", Nammo, missilecat(MissileCategory), Tammo) else diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 8e52ecb58..d539dd098 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2651,8 +2651,10 @@ function CONTROLLABLE:OptionAlarmStateGreen() if self:IsGround() then Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + elseif self:IsShip() then + -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! + --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) end return self From a2790f500c9c6f32a5909491d59aec202e8793b5 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 6 Jun 2018 00:51:59 +0200 Subject: [PATCH 10/16] ARTY v0.9.93 added new mark parameters LLDMS coordinate assignment --- Moose Development/Moose/Core/Point.lua | 22 + .../Moose/Functional/Artillery.lua | 388 ++++++++++++------ 2 files changed, 283 insertions(+), 127 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index bf20bc91b..543bce752 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -251,6 +251,28 @@ do -- COORDINATE return { x = self.x, y = self.z } end + --- Returns the coordinate from the latitude and longitude given in decimal degrees. + -- @param #COORDINATE self + -- @param #number latitude Latitude in decimal degrees. + -- @param #number longitude Longitude in decimal degrees. + -- @param #number altitude (Optional) Altitude in meters. Default is the land height at the coordinate. + -- @return #COORDINATE + function COORDINATE:NewFromLLDD( latitude, longitude, altitude) + + -- Returns a point from latitude and longitude in the vec3 format. + local vec3=coord.LLtoLO(latitude, longitude) + + -- Convert vec3 to coordinate object. + local _coord=self:NewFromVec3(vec3) + + -- Adjust height + if altitude==nil then + _coord.y=altitude + end + + return _coord + end + --- Returns if the 2 coordinates are at the same 2D position. -- @param #COORDINATE self diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 5bec76c49..48d2ab37c 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -531,7 +531,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.92" +ARTY.version="0.9.93" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1139,9 +1139,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) if _dbproperties~=nil then for property,value in pairs(_dbproperties) do self:T({property=property, value=value}) - --if self[property]==nil then self[property]=value - --end end end @@ -1475,14 +1473,13 @@ function ARTY:onEvent(Event) local batteryname=self.Controllable:GetName() local batterycoalition=self.Controllable:GetCoalition() - self:T(string.format("Event captured = %s", tostring(batteryname))) - self:T(string.format("Event id = %s", tostring(Event.id))) - self:T(string.format("Event time = %s", tostring(Event.time))) - self:T(string.format("Event idx = %s", tostring(Event.idx))) - self:T(string.format("Event coalition = %s", tostring(Event.coalition))) - self:T(string.format("Event group id = %s", tostring(Event.groupID))) - self:T(string.format("Event text = %s", tostring(Event.text))) - self:E({eventid=Event.id, vec3=Event.pos}) + self:T2(string.format("Event captured = %s", tostring(batteryname))) + self:T2(string.format("Event id = %s", tostring(Event.id))) + self:T2(string.format("Event time = %s", tostring(Event.time))) + self:T2(string.format("Event idx = %s", tostring(Event.idx))) + self:T2(string.format("Event coalition = %s", tostring(Event.coalition))) + self:T2(string.format("Event group id = %s", tostring(Event.groupID))) + self:T2(string.format("Event text = %s", tostring(Event.text))) if Event.initiator~=nil then local _unitname=Event.initiator:getName() self:T(string.format("Event ini unit name = %s", tostring(_unitname))) @@ -1589,6 +1586,11 @@ function ARTY:_OnEventMarkChange(Event) -- Evaluate marker text and extract parameters. local _assign=self:_Markertext(Event.text) + + -- Check if ENGAGE or MOVE or REQUEST keywords were found. + if _assign==nil or not (_assign.engage or _assign.move or _assign.request) then + return + end -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. local _assigned=true @@ -1606,11 +1608,6 @@ function ARTY:_OnEventMarkChange(Event) if not _assigned then return end - - -- Check if ENGAGE or MOVE or REQUEST keywords were found. - if not (_assign.engage or _assign.move or _assign.request) then - return - end -- Check if the authorization key is required and if it is valid. local _validkey=self:_MarkerKeyAuthentification(Event.text) @@ -1620,7 +1617,16 @@ function ARTY:_OnEventMarkChange(Event) if _assign.requestammo then self:_MarkRequestAmmo() end - -- Done! + if _assign.requestmoves then + self:_MarkRequestMoves() + end + if _assign.requesttargets then + self:_MarkRequestTargets() + end + if _assign.requestrearming then + self:Rearm() + end + -- Requests Done ==> End of story! return end @@ -1649,6 +1655,11 @@ function ARTY:_OnEventMarkChange(Event) -- Also I don't know who can see the mark which was created. _coord:RemoveMark(Event.idx) + -- Coordinate was given in text, e.g. as lat, long. + if _assign.coord then + _coord=_assign.coord + end + -- Anticipate marker ID. -- WARNING: Make sure, no marks are set until the COORDINATE:MarkToCoalition() is called or the target/move name will be wrong and target cannot be removed by deleting its marker. local _id=UTILS._MarkID+1 @@ -2594,7 +2605,7 @@ end function ARTY:_Move(group, ToCoord, Speed, OnRoad) -- Clear all tasks. - group:ClearTasks() + --group:ClearTasks() group:OptionAlarmStateGreen() group:OptionROEHoldFire() @@ -2983,122 +2994,147 @@ function ARTY:_Markertext(text) assignment.readonly=false assignment.canceltarget=false assignment.cancelcurrent=false - --assignment.time=nil - --assignment.nshells=nil - --assignment.prio=nil - --assignment.maxengage=nil - --assignment.radius=nil - --assignment.weapontype=nil - --assignment.speed=nil - --assignment.onroad=nil - --assignment.key=nil - if text:lower():find("arty") then - if text:lower():find("engage") then - assignment.engage=true - elseif text:lower():find("move") then - assignment.move=true - elseif text:lower():find("request") then - assignment.request=true - else - self:E(ARTY.id.."ERROR: Neither ENGAGE nor MOVE keyword specified!") - return - end + -- Check for correct keywords. + if text:lower():find("arty engage") or text:lower():find("arty attack") then + assignment.engage=true + elseif text:lower():find("arty move") or text:lower():find("arty relocate") then + assignment.move=true + elseif text:lower():find("arty request") then + assignment.request=true + else + self:E(ARTY.id..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" keyword specified!') + return nil + end - -- keywords are split by "," - local keywords=self:_split(text, ",") + -- keywords are split by "," + local keywords=self:_split(text, ",") + + for _,key in pairs(keywords) do - for _,key in pairs(keywords) do + local s=self:_split(key, " ") + local val=s[2] + + -- Battery name, i.e. which ARTY group should fire. + if key:lower():find("battery") then + + local v=self:_split(key, '"') + + for i=2,#v,2 do + table.insert(assignment.battery, v[i]) + self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) + end + + elseif key:lower():find("time") then - local s=self:_split(key, " ") - local val=s[2] + if val:lower():find("now") then + assignment.time=self:_SecondsToClock(timer.getTime0()+2) + else + assignment.time=val + end + self:T2(ARTY.id..string.format("Key Time=%s.", val)) + + elseif key:lower():find("shot") then - -- Battery name, i.e. which ARTY group should fire. - if key:lower():find("battery") then + assignment.nshells=tonumber(s[2]) + self:T(ARTY.id..string.format("Key Shot=%s.", val)) + + elseif key:lower():find("prio") then + + assignment.prio=tonumber(val) + self:T2(string.format("Key Prio=%s.", val)) + + elseif key:lower():find("maxengage") then + + assignment.maxengage=tonumber(val) + self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) + + elseif key:lower():find("radius") then + + assignment.radius=tonumber(val) + self:T2(ARTY.id..string.format("Key Radius=%s.", val)) + + elseif key:lower():find("weapon") then + + if val:lower():find("cannon") then + assignment.weapontype=ARTY.WeaponType.Cannon + elseif val:lower():find("rocket") then + assignment.weapontype=ARTY.WeaponType.Rockets + elseif val:lower():find("missile") then + assignment.weapontype=ARTY.WeaponType.GuidedMissile + elseif val:lower():find("nuke") then + assignment.weapontype=ARTY.WeaponType.TacticalNukes + else + assignment.weapontype=ARTY.WeaponType.Auto + end + self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) + + elseif key:lower():find("speed") then + + assignment.speed=tonumber(val) + self:T2(ARTY.id..string.format("Key Speed=%s.", val)) + + elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then + + assignment.onroad=true + self:T2(ARTY.id..string.format("Key Onroad=true.")) + + elseif key:lower():find("irrevocable") or key:lower():find("readonly") then + + assignment.readonly=true + self:T2(ARTY.id..string.format("Key Readonly=true.")) + + elseif key:lower():find("canceltarget") then + + assignment.canceltarget=true + self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) + + elseif 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 + + assignment.requestrearming=true + self:T2(ARTY.id..string.format("Key Request Rearming=true.")) + + elseif assignment.request and key:lower():find("ammo") then + + assignment.requestammo=true + self:T2(ARTY.id..string.format("Key Request Ammo=true.")) + + elseif assignment.request and key:lower():find("target") then + + assignment.requesttargets=true + self:T2(ARTY.id..string.format("Key Request Targets=true.")) + + elseif assignment.request and (key:lower():find("move") or key:lower():find("relocation")) then + + assignment.requestmoves=true + self:T2(ARTY.id..string.format("Key Request Moves=true.")) + + elseif key:lower():find("lldms") then + + local _flat = "%d+:%d+:%d+%s*[N,S]" + local _flon = "%d+:%d+:%d+%s*[W,E]" + local _lat=key:match(_flat) + local _lon=key:match(_flon) + self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s", _lat,_lon)) + + if _lat and _lon then + + -- Convert DMS string to DD numbers format. + local _latitude, _longitude=self:_LLDMS2DD(_lat, _lon) + self:T2(ARTY.id..string.format("Key LLDMS: lat=%.3f, long=%.3f", _latitude,_longitude)) - local v=self:_split(key, '"') - - for i=2,#v,2 do - table.insert(assignment.battery, v[i]) - self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) + -- Convert LL to coordinate object. + if _latitude and _longitude then + assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude) end - elseif key:lower():find("time") then - - if val:lower():find("now") then - assignment.time=self:_SecondsToClock(timer.getTime0()+5) - else - assignment.time=val - end - self:T2(ARTY.id..string.format("Key Time=%s.", val)) - - elseif key:lower():find("shot") then - - assignment.nshells=tonumber(s[2]) - self:T(ARTY.id..string.format("Key Shot=%s.", val)) - - elseif key:lower():find("prio") then - - assignment.prio=tonumber(val) - self:T2(string.format("Key Prio=%s.", val)) - - elseif key:lower():find("maxengage") then - - assignment.maxengage=tonumber(val) - self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) - - elseif key:lower():find("radius") then - - assignment.radius=tonumber(val) - self:T2(ARTY.id..string.format("Key Radius=%s.", val)) - - elseif key:lower():find("weapon") then - - if val:lower():find("cannon") then - assignment.weapontype=ARTY.WeaponType.Cannon - elseif val:lower():find("rocket") then - assignment.weapontype=ARTY.WeaponType.Rockets - elseif val:lower():find("missile") then - assignment.weapontype=ARTY.WeaponType.GuidedMissile - elseif val:lower():find("nuke") then - assignment.weapontype=ARTY.WeaponType.TacticalNukes - else - assignment.weapontype=ARTY.WeaponType.Auto - end - self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) - - elseif key:lower():find("speed") then - - assignment.speed=tonumber(val) - self:T2(ARTY.id..string.format("Key Speed=%s.", val)) - - elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then - - assignment.onroad=true - self:T2(ARTY.id..string.format("Key Onroad=true.")) - - elseif key:lower():find("irrevocable") or key:lower():find("readonly") then - - assignment.readonly=true - self:T2(ARTY.id..string.format("Key Readonly=true.")) - - elseif key:lower():find("canceltarget") then - - assignment.canceltarget=true - self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) - - elseif 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("ammo") then - assignment.requestammo=true - end - - end - else - self:T2(ARTY.id..string.format("This is NO arty command:\n%s", tostring(text))) + end + end end return assignment @@ -3110,6 +3146,44 @@ function ARTY:_MarkRequestAmmo() self:GetAmmo(true) end +--- Request Moves. +-- @param #ARTY self +function ARTY:_MarkRequestMoves() + local text=string.format("%s, relocations:", self.Controllable:GetName()) + if self.currentMove then + text=text..string.format("\n- %s", self:_MoveInfo(self.currentMove)) + else + text=text..string.format("\n- no current relocation") + end + if #self.moves>0 then + for _,move in pairs(self.moves) do + text=text..string.format("\n- %s", self:_MoveInfo(move)) + end + else + text=text..string.format("\n- no more relocations") + end + MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) +end + +--- Request Targets. +-- @param #ARTY self +function ARTY:_MarkRequestTargets() + local text=string.format("%s, targets:", self.Controllable:GetName()) + if self.currentTarget then + text=text..string.format("\n- %s", self:_TargetInfo(self.currentTarget)) + else + text=text..string.format("\n- no current target") + end + if #self.targets>0 then + for _,target in pairs(self.targets) do + text=text..string.format("\n- %s", self:_TargetInfo(target)) + end + else + text=text..string.format("\n- no more targets") + end + MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) +end + --- Create a name for an engagement initiated by placing a marker. -- @param #ARTY self -- @param #number markerid ID of the placed marker. @@ -3320,7 +3394,7 @@ function ARTY:_CheckTargetsInRange() MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) -- Assign relocation move. - self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) + self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) end @@ -3716,6 +3790,66 @@ function ARTY:_MoveInfo(move) return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s", move.name, _clock, move.speed, tostring(move.onroad), tostring(move.cancel)) end +--- Convert Latitude and Lontigude from DMS to DD. +-- @param #ARTY self +-- @param #string l1 Latitude or longitude as string in the format DD:MM:SS N/S/W/E +-- @param #string l2 Latitude or longitude as string in the format DD:MM:SS N/S/W/E +-- @return #number Latitude in decimal degree format. +-- @return #number Longitude in decimal degree format. +function ARTY:_LLDMS2DD(l1,l2) + self:F2(l1,l2) + + -- Make an array of lat and long. + local _latlong={l1,l2} + + local _latitude=nil + local _longitude=nil + + for _,ll in pairs(_latlong) do + + -- Format is expected as "DD:MM:SS" or "D:M:S". + local _format = "%d+:%d+:%d+" + local _ldms=ll:match(_format) + + if ldms then + + -- Split DMS to degrees, minutes and seconds. + local _dms=self:_split(_ldms, ":") + local _deg=tonumber(_dms[1]) + local _min=tonumber(_dms[2]) + local _sec=tonumber(_dms[3]) + + -- Convert DMS to DD. + local function DMS2DD(d,m,s) + return d+m/60+s/3600 + end + + -- Detect with hemisphere is meant. + if ll:match("N") then + _latitude=DMS2DD(_deg,_min,_sec) + elseif ll:match("S") then + _latitude=-DMS2DD(_deg,_min,_sec) + elseif ll:match("W") then + _longitude=-DMS2DD(_deg,_min,_sec) + elseif ll:match("E") then + _longitude=DMS2DD(_deg,_min,_sec) + end + + -- Debug text. + local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec) + self:T2(ARTY.id..text) + + end + end + + -- Debug text. + local text=string.format("\nLatitude %.3f", _latitude) + text=text..string.format("\nLongitude %.3f", _longitude) + self:T2(ARTY.id..text) + + return _latitude,_longitude +end + --- Convert time in seconds to hours, minutes and seconds. -- @param #ARTY self -- @param #number seconds Time in seconds. From 38a03f4cbc06fff4204fe015f9fdf3f4d378cd69 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 6 Jun 2018 22:57:04 +0200 Subject: [PATCH 11/16] ARTY v0.9.94 Bug fixes and improvements. --- .../Moose/Functional/Artillery.lua | 164 +++++++++++++----- 1 file changed, 117 insertions(+), 47 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 48d2ab37c..7920bb774 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -277,8 +277,8 @@ -- Targets and relocations can be assigned by players via placing a mark on the F10 map. The marker text must contain certain keywords. -- -- This feature can be turned on with the @{#ARTY.SetMarkAssignmentsOn}(*key*). The parameter *key* is optional. When set, it can be used as PIN, i.e. only --- player who know the correct key are able to assign targets or relocations. Default behavior is that all players belonging to the same coalition as the --- ARTY group are able to assign targets and moves. +-- players who know the correct key are able to assign and cancel targets or relocations. Default behavior is that all players belonging to the same coalition as the +-- ARTY group are able to assign targets and moves without a key. -- -- ### Target Assignments -- A new target can be assigned by writing **arty engage** in the marker text. This can be followed by a comma separated lists of optional keywords and parameters: @@ -297,7 +297,8 @@ -- arty engage! -- arty engage! shots 20, prio 10, time 08:15, weapon cannons -- arty engage! battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15 --- arty engage! battery "Blue Paladin 1", key 666 +-- arty engage! battery "Blue MRLS 1", key 666 +-- arty engage, battery "Paladin Alpha", weapon nukes, shots 1, time 20:15 -- -- 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. @@ -315,9 +316,33 @@ -- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker. -- -- Here are some examples: --- arty move! time 23:45, speed 50, onroad, cancel --- arty move! battery "Blue Paladin", onroad --- arty move, cancel, speed 10, onroad +-- arty move +-- arty move! time 23:45, speed 50, on road +-- arty move! battery "Blue Paladin" +-- arty move, battery "Blue MRLS", canceltarget, speed 10, on road +-- +-- ### Coordinate Independent Commands +-- +-- 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 +-- * *target* All assigned targets are reported. +-- * *move* All assigned relocation moves are reported. +-- * *ammo* Current ammunition status is reported. +-- +-- For example +-- arty request, ammo +-- arty request, battery "Paladin Bravo", targets +-- arty request, battery "MRLS Charly", move +-- -- -- ## Fine Tuning -- @@ -414,6 +439,7 @@ ARTY={ Nmissiles0=0, Nukes0=0, FullAmmo=0, + defaultROE="weapon_hold", StatusInterval=10, WaitForShotTime=300, DCSdesc=nil, @@ -531,7 +557,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.93" +ARTY.version="0.9.94" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -556,6 +582,8 @@ ARTY.version="0.9.93" -- TODO: Improve handling of special weapons. When winchester if using selected weapons? -- TODO: Handle rearming for ships. -- TODO: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. +-- TODO: Add set commands via markers. E.g. set rearming place. +-- TODO: Test stationary types like mortas ==> rearming etc. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1216,6 +1244,9 @@ function ARTY:onafterStart(Controllable, From, Event, To) self:T(ARTY.id..text) end + -- Set default ROE to weapon hold. + self.Controllable:OptionROEHoldFire() + -- Add event handler. self:HandleEvent(EVENTS.Shot, self._OnEventShot) self:HandleEvent(EVENTS.Dead, self._OnEventDead) @@ -1246,7 +1277,13 @@ end --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self -function ARTY:_StatusReport() +-- @param #boolean display (Optional) If true, send message to coalition. Default false. +function ARTY:_StatusReport(display) + + -- Set default. + if display==nil then + display=false + end -- Get Ammo. local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() @@ -1286,6 +1323,7 @@ function ARTY:_StatusReport() end text=text..string.format("******************************************************") env.info(ARTY.id..text) + MESSAGE:New(text, 20):Clear():ToCoalitionIf(self.Controllable:GetCoalition(), display) end @@ -1623,6 +1661,9 @@ function ARTY:_OnEventMarkChange(Event) if _assign.requesttargets then self:_MarkRequestTargets() end + if _assign.requeststatus then + self:_MarkRequestStatus() + end if _assign.requestrearming then self:Rearm() end @@ -1646,6 +1687,8 @@ function ARTY:_OnEventMarkChange(Event) if _validkey then -- Convert (wrong x-->z, z-->x) vec3 + -- TODO: This needs to be "fixed", once DCS gives the correct numbers for x and z. + -- local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z} local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} -- Get coordinate from vec3. @@ -1807,6 +1850,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) if self:is("Rearmed") then local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) self:T2(ARTY.id..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m", Controllable:GetName(), distance)) + -- Check that ARTY group is back and set it to combat ready. if distance <= self.RearmingDistance then self:T2(ARTY.id..string.format("%s: Rearmed ==> CombatReady", Controllable:GetName())) self:CombatReady() @@ -2062,6 +2106,9 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) self:RemoveTarget(target.name) end + -- Set ROE to weapon hold. + self.Controllable:OptionROEHoldFire() + -- Clear tasks. self.Controllable:ClearTasks() @@ -2105,6 +2152,14 @@ end function ARTY:onbeforeRearm(Controllable, From, Event, To) self:_EventFromTo("onbeforeRearm", Event, From, To) + local _rearmed=self:_CheckRearmed() + if _rearmed then + self:T(ARTY.id..string.format("%s, group is already armed to the teeth. Rearming request denied!", self.Controllable:GetName())) + return false + else + self:T(ARTY.id..string.format("%s, group might be rearmed.", self.Controllable:GetName())) + end + -- Check if a reaming unit or rearming place was specified. if self.RearmingGroup and self.RearmingGroup:IsAlive() then return true @@ -2128,6 +2183,9 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Coordinate of ARTY unit. local coordARTY=self.Controllable:GetCoordinate() + -- Remember current coordinates so that we find our way back home. + self.InitialCoord=coordARTY + -- Coordinate of rearming group. local coordRARM=nil if self.RearmingGroup then @@ -2152,7 +2210,9 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Route ARTY group to rearming place. if dA > self.RearmingDistance then - self:Move(self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2), self.Speed, self.RearmingArtyOnRoad) + local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2) + self:AssignMoveCoord(_tocoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "Relocate to rearming place", true) + --self:Move(, self.Speed, self.RearmingArtyOnRoad) end -- Route Rearming group to rearming place. @@ -2193,7 +2253,8 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Route ARTY group to rearming place. if dA > self.RearmingDistance then - self:Move(self:_VicinityCoord(self.RearmingPlaceCoord), self.Speed, self.RearmingArtyOnRoad) + local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) + self:AssignMoveCoord(_tocoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "Relocate to rearming place", true) end end @@ -2222,7 +2283,8 @@ function ARTY:onafterRearmed(Controllable, From, Event, To) -- Route ARTY group back to where it came from (if distance is > 100 m). local d1=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) if d1 > self.RearmingDistance then - self:Move(self.InitialCoord, self.Speed, self.RearmingArtyOnRoad) + --self:Move(self.InitialCoord, self.Speed, self.RearmingArtyOnRoad) + self:AssignMoveCoord(self.InitialCoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "After rearm back to initial pos", true) end -- Route unit back to where it came from (if distance is > 100 m). @@ -2370,9 +2432,6 @@ end function ARTY:onafterNewTarget(Controllable, From, Event, To, target) self:_EventFromTo("onafterNewTarget", Event, From, To) - -- Check if target is in range. - --local _inrange, _toofar, _tooclose=self:_TargetInRange(target, self.report or self.Debug) - -- Debug message. local text=string.format("Adding new target %s.", target.name) MESSAGE:New(text, 5):ToAllIf(self.Debug) @@ -2569,7 +2628,7 @@ function ARTY:_NuclearBlast(_coord) -- Distance from group to impact point. local distance= spos:Get2DDistance(_coord) - -- Place markers on every possible scenery object. + -- 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()) @@ -3009,6 +3068,7 @@ function ARTY:_Markertext(text) -- keywords are split by "," local keywords=self:_split(text, ",") + self:T({keywords=keywords}) for _,key in pairs(keywords) do @@ -3025,7 +3085,7 @@ function ARTY:_Markertext(text) self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) end - elseif key:lower():find("time") then + elseif (assignment.engage or assignment.move) and key:lower():find("time") then if val:lower():find("now") then assignment.time=self:_SecondsToClock(timer.getTime0()+2) @@ -3034,27 +3094,27 @@ function ARTY:_Markertext(text) end self:T2(ARTY.id..string.format("Key Time=%s.", val)) - elseif key:lower():find("shot") then + elseif assignment.engage and key:lower():find("shot") then assignment.nshells=tonumber(s[2]) self:T(ARTY.id..string.format("Key Shot=%s.", val)) - elseif key:lower():find("prio") then + elseif assignment.engage and key:lower():find("prio") then assignment.prio=tonumber(val) self:T2(string.format("Key Prio=%s.", val)) - elseif key:lower():find("maxengage") then + elseif assignment.engage and key:lower():find("maxengage") then assignment.maxengage=tonumber(val) self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) - elseif key:lower():find("radius") then + elseif assignment.engage and key:lower():find("radius") then assignment.radius=tonumber(val) self:T2(ARTY.id..string.format("Key Radius=%s.", val)) - elseif key:lower():find("weapon") then + elseif assignment.engage and key:lower():find("weapon") then if val:lower():find("cannon") then assignment.weapontype=ARTY.WeaponType.Cannon @@ -3069,12 +3129,12 @@ function ARTY:_Markertext(text) end self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) - elseif key:lower():find("speed") then + elseif assignment.move and key:lower():find("speed") then assignment.speed=tonumber(val) self:T2(ARTY.id..string.format("Key Speed=%s.", val)) - elseif key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")then + elseif assignment.move and (key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")) then assignment.onroad=true self:T2(ARTY.id..string.format("Key Onroad=true.")) @@ -3084,12 +3144,12 @@ function ARTY:_Markertext(text) assignment.readonly=true self:T2(ARTY.id..string.format("Key Readonly=true.")) - elseif key:lower():find("canceltarget") then + elseif assignment.move and key:lower():find("canceltarget") then assignment.canceltarget=true self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) - elseif key:lower():find("cancelcurrent") then + 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.")) @@ -3108,7 +3168,12 @@ function ARTY:_Markertext(text) assignment.requesttargets=true self:T2(ARTY.id..string.format("Key Request Targets=true.")) - + + elseif assignment.request and key:lower():find("status") then + + assignment.requeststatus=true + self:T2(ARTY.id..string.format("Key Request Status=true.")) + elseif assignment.request and (key:lower():find("move") or key:lower():find("relocation")) then assignment.requestmoves=true @@ -3140,27 +3205,32 @@ function ARTY:_Markertext(text) return assignment end ---- Request ammo. +--- Request ammo via mark. -- @param #ARTY self function ARTY:_MarkRequestAmmo() self:GetAmmo(true) end +--- Request status via mark. +-- @param #ARTY self +function ARTY:_MarkRequestStatus() + self:_StatusReport(true) +end + --- Request Moves. -- @param #ARTY self function ARTY:_MarkRequestMoves() local text=string.format("%s, relocations:", self.Controllable:GetName()) - if self.currentMove then - text=text..string.format("\n- %s", self:_MoveInfo(self.currentMove)) - else - text=text..string.format("\n- no current relocation") - end if #self.moves>0 then for _,move in pairs(self.moves) do - text=text..string.format("\n- %s", self:_MoveInfo(move)) + if self.currentMove and move.name == self.currentMove.name then + text=text..string.format("\n- %s (current)", self:_MoveInfo(move)) + else + text=text..string.format("\n- %s", self:_MoveInfo(move)) + end end else - text=text..string.format("\n- no more relocations") + text=text..string.format("\n- no queued relocations") end MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) end @@ -3169,17 +3239,16 @@ end -- @param #ARTY self function ARTY:_MarkRequestTargets() local text=string.format("%s, targets:", self.Controllable:GetName()) - if self.currentTarget then - text=text..string.format("\n- %s", self:_TargetInfo(self.currentTarget)) - else - text=text..string.format("\n- no current target") - end if #self.targets>0 then for _,target in pairs(self.targets) do - text=text..string.format("\n- %s", self:_TargetInfo(target)) + if self.currentTarget and target.name == self.currentTarget.name then + text=text..string.format("\n- %s (current)", self:_TargetInfo(target)) + else + text=text..string.format("\n- %s", self:_TargetInfo(target)) + end end else - text=text..string.format("\n- no more targets") + text=text..string.format("\n- no queued targets") end MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) end @@ -3200,7 +3269,7 @@ function ARTY:_MarkMoveName(markerid) return string.format("BATTERY=%s, Marked Relocation ID=%d", self.Controllable:GetName(), markerid) end ---- Create a name for a relocation move initiated by placing a marker. +--- Get the marker ID from the assigned task name. -- @param #ARTY self -- @param #string name Name of the assignment. -- @return #string Name of the ARTY group or nil @@ -3552,14 +3621,14 @@ function ARTY:_GetTargetIndexByName(name) for i=1,#self.targets do local targetname=self.targets[i].name - self:T(ARTY.id..string.format("Have target with name %s. Index = %d", targetname, i)) + self:T3(ARTY.id..string.format("Have target with name %s. Index = %d", targetname, i)) if targetname==name then - self:T(ARTY.id..string.format("Found target with name %s. Index = %d", name, i)) + self:T2(ARTY.id..string.format("Found target with name %s. Index = %d", name, i)) return i end end - self:E(ARTY.id..string.format("ERROR: Target with name %s could not be found!", name)) + self:T2(ARTY.id..string.format("WARNING: Target with name %s could not be found. (This can happen.)", name)) return nil end @@ -3572,13 +3641,14 @@ function ARTY:_GetMoveIndexByName(name) for i=1,#self.moves do local movename=self.moves[i].name + self:T3(ARTY.id..string.format("Have move with name %s. Index = %d", movename, i)) if movename==name then self:T2(ARTY.id..string.format("Found move with name %s. Index = %d", name, i)) return i end end - self:E(ARTY.id..string.format("ERROR: Move with name %s could not be found!", name)) + self:T2(ARTY.id..string.format("WARNING: Move with name %s could not be found. (This can happen.)", name)) return nil end @@ -3739,7 +3809,7 @@ function ARTY:_VicinityCoord(coord, rmin, rmax) local vec2=coord:GetRandomVec2InRadius(rmax, rmin) local pops=COORDINATE:NewFromVec2(vec2) -- Debug info. - self:T(ARTY.id..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)", pops:Get2DDistance(coord), rmin, rmax)) + self:T3(ARTY.id..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)", pops:Get2DDistance(coord), rmin, rmax)) return pops end From c6fc571c957b73acd0f4677f08df86f76d831edf Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 8 Jun 2018 00:00:41 +0200 Subject: [PATCH 12/16] ARTY adjusted docu some improvemens --- Moose Development/Moose/Core/Point.lua | 25 ++- .../Moose/Functional/Artillery.lua | 143 +++++++++++------- 2 files changed, 103 insertions(+), 65 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 543bce752..b7688ad82 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -957,26 +957,25 @@ do -- COORDINATE -- The first point is the closest point on road of the given coordinate. The last point is the closest point on road of the ToCoord. Hence, the coordinate itself and the final ToCoord are not necessarily included in the path. -- @param #COORDINATE self -- @param #COORDINATE ToCoord Coordinate of destination. - -- @return #table Table of coordinates on road. + -- @return #table Table of coordinates on road. If no path on road can be found, nil is returned. function COORDINATE:GetPathOnRoad(ToCoord) -- DCS API function returning a table of vec2. local path = land.findPathOnRoads("roads", self.x, self.z, ToCoord.x, ToCoord.z) - --Path[#Path+1]=COORDINATE:NewFromVec2(path[1]) - --Path[#Path+1]=COORDINATE:NewFromVec2(path[#path]) - --Path[#Path+1]=self:GetClosestPointToRoad() - --Path[#Path+1]=ToCoord:GetClosestPointToRoad() - -- I've removed this stuff because it severely slows down DCS in case of paths with a lot of segments. - -- Just the beginning and the end point is sufficient. - local Path={} - --Path[#Path+1]=self - for i, v in ipairs(path) do - Path[#Path+1]=COORDINATE:NewFromVec2(v) - end - --Path[#Path+1]=ToCoord + if path then + --Path[#Path+1]=self + for i, v in ipairs(path) do + Path[#Path+1]=COORDINATE:NewFromVec2(v) + end + --Path[#Path+1]=ToCoord + else + -- There are cases where no path on road can be found. + return nil + end + return Path end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 7920bb774..fde0f379c 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -2,17 +2,20 @@ -- -- === -- --- The ARTY class can be used to easily assign and manage targets for artillery units. +-- The ARTY class can be used to easily assign and manage targets for artillery units using an advanced queueing system. -- -- ## Features: -- -- * Multiple targets can be assigned. No restriction on number of targets. -- * Targets can be given a priority. Engagement of targets is executed a according to their priority. -- * Engagements can be scheduled, i.e. will be executed at a certain time of the day. +-- * Multiple relocations of the group can be assigned and scheduled via queueing system. -- * Special weapon types can be selected for each attack, e.g. cruise missiles for Naval units. --- * Automatic rearming once the artillery is out of ammo. +-- * Automatic rearming once the artillery is out of ammo (optional). +-- * Automatic relocation after each firing engagement to prevent counter strikes (optional). +-- * Automatic relocation movements to get the battery within firing range (optional). +-- * Simulation of tactical nuclear shells. -- * New targets can be added during the mission, e.g. when they are detected by recon units. --- * Modeling of tactical nuclear shells. -- * Targets and relocations can be assigned by placing markers on the F10 map. -- * Finite state machine implementation. Mission designer can interact when certain events occur. -- @@ -276,31 +279,31 @@ -- -- Targets and relocations can be assigned by players via placing a mark on the F10 map. The marker text must contain certain keywords. -- --- This feature can be turned on with the @{#ARTY.SetMarkAssignmentsOn}(*key*). The parameter *key* is optional. When set, it can be used as PIN, i.e. only +-- This feature can be turned on with the @{#ARTY.SetMarkAssignmentsOn}(*key*, *readonly*). The parameter *key* is optional. When set, it can be used as PIN, i.e. only -- players who know the correct key are able to assign and cancel targets or relocations. Default behavior is that all players belonging to the same coalition as the -- ARTY group are able to assign targets and moves without a key. -- -- ### Target Assignments --- A new target can be assigned by writing **arty engage** in the marker text. This can be followed by a comma separated lists of optional keywords and parameters: +-- A new target can be assigned by writing **arty engage** in the marker text. This can be followed by a **comma separated list** of optional keywords and parameters: -- -- * *time* Time for which which the engagement is schedules, e.g. 08:42. Default is as soon as possible. --- * *prio* Priority of the engagement as number between 1 (high prio) and 100 (low prio). Default is 50. +-- * *prio* Priority of the engagement as number between 1 (high prio) and 100 (low prio). Default is 50, i.e. medium priority. -- * *shots* Number of shots (shells, rockets or missiles) fired at each engagement. Default is 5. -- * *maxengage* Number of times the target is engaged. Default is 1. -- * *radius* Scattering radius of the fired shots in meters. Default is 100 m. -- * *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. --- * *readonly* Marker cannot be deleted by users any more. 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: --- arty engage! --- arty engage! shots 20, prio 10, time 08:15, weapon cannons --- arty engage! battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15 --- arty engage! battery "Blue MRLS 1", key 666 +-- arty engage +-- arty engage, shots 20, prio 10, time 08:15, weapon cannons +-- 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 "Paladin Alpha", weapon nukes, shots 1, time 20:15 -- --- 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. -- -- ### Relocation Assignments @@ -317,8 +320,8 @@ -- -- Here are some examples: -- arty move --- arty move! time 23:45, speed 50, on road --- arty move! battery "Blue Paladin" +-- arty move, time 23:45, speed 50, on road +-- arty move, battery "Blue Paladin" -- arty move, battery "Blue MRLS", canceltarget, speed 10, on road -- -- ### Coordinate Independent Commands @@ -334,6 +337,7 @@ -- ### 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 +-- -- * *target* All assigned targets are reported. -- * *move* All assigned relocation moves are reported. -- * *ammo* Current ammunition status is reported. @@ -348,6 +352,8 @@ -- -- The mission designer has a few options to tailor the ARTY object according to his needs. -- +-- * @{#ARTY.SetAutomaticRelocate}(*maxdist*, *onroad*) lets the ARTY group automatically move to within firing range if a current target is outside the min/max firing range. The +-- optional parameter *maxdist* is the maximum distance im km the group will move. If the distance is greater no relocation is performed. Default is 50 km. -- * @{#ARTY.SetRelocateAfterEngagement}() will cause the ARTY group to change its position after each firing assignment. -- * @{#ARTY.SetRelocateDistance}(*rmax*, *rmin*) sets the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. -- * @{#ARTY.RemoveAllTargets}() removes all targets from the target queue. @@ -1423,36 +1429,7 @@ function ARTY:_OnEventShot(EventData) 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 uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype)) - - -- Special weapon type requested ==> Check if corresponding ammo is empty. - local _partlyoutofammo=false - if self.currentTarget.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then - - self:T(ARTY.id..string.format("Group %s, cannons requested but shells empty.", self.Controllable:GetName())) - _partlyoutofammo=true - - 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 - - self:T(ARTY.id..string.format("Group %s, rockets requested but rockets empty.", self.Controllable:GetName())) - _partlyoutofammo=true - - elseif self.currentTarget.weapontype==ARTY.WeaponType.UnguidedAny and _nshells+_nrockets==0 then - - self:T(ARTY.id..string.format("Group %s, unguided weapon requested but shells AND rockets empty.", self.Controllable:GetName())) - _partlyoutofammo=true - - 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..string.format("Group %s, guided, anti-ship or cruise missiles requested but all missiles empty.", self.Controllable:GetName())) - _partlyoutofammo=true - - end - + -- Check if number of shots reached max. local _ceasefire=false local _relocate=false @@ -1469,6 +1446,10 @@ function ARTY:_OnEventShot(EventData) end end + -- Check if we are partly out of ammo. + -- TODO: move this to status. + local _partlyoutofammo=self:_CheckOutOfAmmo() + -- Check if we are (partly) out of ammo. if _outofammo or _partlyoutofammo then _ceasefire=true @@ -1497,6 +1478,51 @@ function ARTY:_OnEventShot(EventData) end end +--- Check if group is (partly) out of ammo. +-- @param #ARTY self +-- @return @boolean True if any target in the queue requests a weapon type that is null. +function ARTY:_CheckOutOfAmmo() + + -- 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(self.targets) do + + if Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then + + self:T(ARTY.id..string.format("Group %s, cannons requested but shells empty.", self.Controllable:GetName())) + _partlyoutofammo=true + + elseif Target.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 Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then + + self:T(ARTY.id..string.format("Group %s, rockets requested but rockets empty.", self.Controllable:GetName())) + _partlyoutofammo=true + + elseif Target.weapontype==ARTY.WeaponType.UnguidedAny and _nshells+_nrockets==0 then + + self:T(ARTY.id..string.format("Group %s, unguided weapon requested but shells AND rockets empty.", self.Controllable:GetName())) + _partlyoutofammo=true + + elseif (Target.weapontype==ARTY.WeaponType.GuidedMissile or Target.weapontype==ARTY.WeaponType.CruiseMissile or Target.weapontype==ARTY.WeaponType.AntiShipMissile) and _nmissiles==0 then + + self:T(ARTY.id..string.format("Group %s, guided, anti-ship or cruise missiles requested but all missiles empty.", self.Controllable:GetName())) + _partlyoutofammo=true + + end + + end + + return _partlyoutofammo +end + --- After "Start" event. Initialized ROE and alarm state. Starts the event handler. -- @param #ARTY self -- @param #table Event @@ -2319,15 +2345,15 @@ function ARTY:_CheckRearmed() -- Rearming status in per cent. local _rearmpc=nammo/self.FullAmmo*100 - -- Send message. + -- Send message if rearming > 1% complete if _rearmpc>1 then - local text=string.format("%s, rearming %d %% complete.", self.Controllable:GetName(), _rearmpc) + local text=string.format("%s, rearming %d %% complete. nammo=%d , fullammo=%d", self.Controllable:GetName(), _rearmpc, nammo, self.FullAmmo) self:T(ARTY.id..text) MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) end -- Return if ammo is full. - if nammo==self.FullAmmo then + if nammo>=self.FullAmmo then return true else return false @@ -2664,7 +2690,7 @@ end function ARTY:_Move(group, ToCoord, Speed, OnRoad) -- Clear all tasks. - --group:ClearTasks() + group:ClearTasks() group:OptionAlarmStateGreen() group:OptionROEHoldFire() @@ -2681,7 +2707,7 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) Speed=math.min(Speed, SpeedMax) -- Current coordinates of group. - local cpini=group:GetCoordinate() + local cpini=group:GetCoordinate() -- Core.Point#COORDINATE -- Distance between current and final point. local dist=cpini:Get2DDistance(ToCoord) @@ -2698,8 +2724,17 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) if OnRoad then -- Path on road (only first and last points) - local _first=cpini:GetClosestPointToRoad() - local _last=ToCoord:GetClosestPointToRoad() + --local _first=cpini:GetClosestPointToRoad() + --local _last=ToCoord:GetClosestPointToRoad() + + local _pathonroad=cpini:GetPathOnRoad(ToCoord) + local _first=_pathonroad[1] + local _last=_pathonroad[#_pathonroad] + + if self.Debug then + _first:SmokeBlue() + _last:SmokeBlue() + end -- First point on road. path[#path+1]=_first:WaypointGround(Speed, "On Road") @@ -2715,6 +2750,10 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) path[#path+1]=ToCoord:WaypointGround(Speed, formation) task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) + if self.Debug then + cpini:SmokeBlue() + ToCoord:SmokeBlue() + end -- Init waypoints of the group. local Waypoints={} From 403f22bd2b1d82bc14110bcc814521f585ab8f37 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 9 Jun 2018 18:33:20 +0200 Subject: [PATCH 13/16] ARTY v0.9.95 Reworked rearming behavior for selected weapons. Many other improvements. --- .../Moose/Functional/Artillery.lua | 519 +++++++++++------- 1 file changed, 319 insertions(+), 200 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index fde0f379c..aa9a043c2 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -354,8 +354,8 @@ -- -- * @{#ARTY.SetAutomaticRelocate}(*maxdist*, *onroad*) lets the ARTY group automatically move to within firing range if a current target is outside the min/max firing range. The -- optional parameter *maxdist* is the maximum distance im km the group will move. If the distance is greater no relocation is performed. Default is 50 km. --- * @{#ARTY.SetRelocateAfterEngagement}() will cause the ARTY group to change its position after each firing assignment. --- * @{#ARTY.SetRelocateDistance}(*rmax*, *rmin*) sets the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. +-- * @{#ARTY.SetRelocateAfterEngagement}(*rmax*, *rmin*) will cause the ARTY group to change its position after each firing assignment. +-- Optional parameters *rmax*, *rmin* define the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. -- * @{#ARTY.RemoveAllTargets}() removes all targets from the target queue. -- * @{#ARTY.RemoveTarget}(*name*) deletes the target with *name* from the target queue. -- * @{#ARTY.SetMaxFiringRange}(*range*) defines the maximum firing range. Targets further away than this distance are not engaged. @@ -563,7 +563,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.94" +ARTY.version="0.9.95" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -811,18 +811,15 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) -- Default is current time if no time was specified. time=time or self:_SecondsToClock(timer.getAbsTime()) - - -- Get max speed of group. - local speedmax=self.Controllable:GetSpeedMax() - + -- Set speed. if speed then - -- Make sure, given speed is less than max phycially possible speed of group. - speed=math.min(speed, speedmax) + -- Make sure, given speed is less than max physiaclly possible speed of group. + speed=math.min(speed, self.SpeedMax) elseif self.Speed then - speed=self.Speed + speed=self.Speed else - speed=speedmax*0.7 + speed=self.SpeedMax*0.7 end -- Default is off road. @@ -1096,19 +1093,10 @@ 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) +function ARTY:SetRelocateAfterEngagement(rmax, rmin) + self.relocateafterfire=true self.relocateRmax=rmax or 800 self.relocateRmin=rmin or 300 end @@ -1221,6 +1209,10 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Targets:\n") for _, target in pairs(self.targets) do text=text..string.format("- %s\n", self:_TargetInfo(target)) + local possible=self:_CheckWeaponTypePossible(target) + if not possible then + self:E(ARTY.id..string.format("WARNING: Selected weapon type %s is not possible", self:_WeaponTypeName(target.weapontype))) + end if self.Debug then local zone=ZONE_RADIUS:New(target.name, target.coord:GetVec2(), target.radius) zone:BoundZone(180, coalition.side.NEUTRAL) @@ -1414,46 +1406,55 @@ function ARTY:_OnEventShot(EventData) -- Get current ammo. local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() - -- Decrease available nukes. + -- Decrease available nukes because we just fired one. if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then self.Nukes=self.Nukes-1 end + -- Check if we are completely out of ammo. 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())) _outofammo=true end + -- Check if we are out of ammo of the weapon type used for this target. + -- Note that should not happen because we only open fire with the available number of shots. + local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget}) + -- Weapon type name for current target. 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 uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype)) - -- Check if number of shots reached max. + local _ceasefire=false local _relocate=false + + -- Check if number of shots reached max. if self.Nshots >= self.currentTarget.nshells then + + -- Debug message local text=string.format("Group %s stop firing on target %s.", self.Controllable:GetName(), self.currentTarget.name) self:T(ARTY.id..text) MESSAGE:New(text, 5):ToAllIf(self.Debug) -- Cease fire. _ceasefire=true - - if self.relocateafterfire then - _relocate=true - end + + -- Relocate if enabled. + _relocate=self.relocateafterfire end - -- Check if we are partly out of ammo. - -- TODO: move this to status. - local _partlyoutofammo=self:_CheckOutOfAmmo() - -- Check if we are (partly) out of ammo. if _outofammo or _partlyoutofammo then _ceasefire=true - end + end + + -- Relocate position. + if _relocate then + self:_Relocate() + end -- Cease fire on current target. if _ceasefire then @@ -1462,26 +1463,22 @@ function ARTY:_OnEventShot(EventData) -- Group is out of ammo (or partly and can rearm) ==> Winchester (==> Rearm). if _outofammo or (_partlyoutofammo and self.RearmingGroup ~=nil) then - self:Winchester() - return + --self:Winchester() + --return end - -- Relocate position - if _relocate then - self:_Relocate() - end - else - self:E(ARTY.id..string.format("ERROR: No current target for group %s?!", self.Controllable:GetName())) + self:E(ARTY.id..string.format("WARNING: No current target for group %s?!", self.Controllable:GetName())) end end end end ---- Check if group is (partly) out of ammo. +--- Check if group is (partly) out of ammo of a special weapon type. -- @param #ARTY self --- @return @boolean True if any target in the queue requests a weapon type that is null. -function ARTY:_CheckOutOfAmmo() +-- @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() @@ -1489,31 +1486,46 @@ function ARTY:_CheckOutOfAmmo() -- Special weapon type requested ==> Check if corresponding ammo is empty. local _partlyoutofammo=false - for _,Target in pairs(self.targets) do + for _,Target in pairs(targets) do - if Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then + 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 but shells empty.", self.Controllable:GetName())) + 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 but nukes empty.", self.Controllable:GetName())) + 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 but rockets empty.", self.Controllable:GetName())) + 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 but shells AND rockets empty.", self.Controllable:GetName())) + 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 or Target.weapontype==ARTY.WeaponType.CruiseMissile or Target.weapontype==ARTY.WeaponType.AntiShipMissile) and _nmissiles==0 then + elseif Target.weapontype==ARTY.WeaponType.GuidedMissile and _nmissiles==0 then - self:T(ARTY.id..string.format("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 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 @@ -1614,16 +1626,22 @@ function ARTY:_OnEventMarkRemove(Event) -- This should be the unique name of the target or move. if _cancelmove then if self.currentMove and self.currentMove.name==_name then + -- We do clear tasks here because in Arrived() it can cause a CTD if the group did actually arrive! self.Controllable:ClearTasks() + -- Current move is removed here. In contrast to RemoveTarget() there are is no maxengage parameter. self:Arrived() else + -- Remove move from queue self:RemoveMove(_name) end elseif _canceltarget then if self.currentTarget and self.currentTarget.name==_name then + -- Cease fire. self:CeaseFire(self.currentTarget) + -- We still need to remove the target, because there might be more planned engagements (maxengage>1). self:RemoveTarget(_name) else + -- Remove target from queue self:RemoveTarget(_name) end end @@ -1852,13 +1870,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) self:_StatusReport() end - -- Group is out of ammo. - if self:is("OutOfAmmo") then - self:T2(ARTY.id..string.format("%s: OutOfAmmo. ==> Rearm", Controllable:GetName())) - self:Rearm() - end - - -- Group is out of moving. + -- Group on the move. if self:is("Moving") then self:T2(ARTY.id..string.format("%s: Moving", Controllable:GetName())) end @@ -1895,10 +1907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To) self:_CheckShootingStarted() end - -- Check if targets are in range and update target.inrange value. self:_CheckTargetsInRange() + -- Check if selected weapon type for target is possible at all. E.g. request rockets for Paladin. + local notpossible={} + for i=1,#self.targets do + local _target=self.targets[i] + local possible=self:_CheckWeaponTypePossible(_target) + if not possible then + table.insert(notpossible, _target.name) + end + end + for _,targetname in pairs(notpossible) do + self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.Controllable:GetName(), targetname)) + self:RemoveTarget(targetname) + end + -- Get a valid timed target if it is due to be attacked. local _timedTarget=self:_CheckTimedTargets() @@ -1908,36 +1933,55 @@ function ARTY:onafterStatus(Controllable, From, Event, To) -- Get a commaned move to another location. local _move=self:_CheckMoves() - if (self:is("CombatReady") or self:is("Firing")) and _move then - -- Group is combat ready or firing but we have a move. - self:T2(ARTY.id..string.format("%s: CombatReady/Firing ==> Move", Controllable:GetName())) + if _move then -- Command to move. - self.currentMove=_move - self:Move(_move.coord, _move.speed, _move.onroad) + self:Move(_move) - elseif self:is("CombatReady") or (self:is("Firing") and _timedTarget) then - -- Group is combat ready or firing but we have a high prio timed target. - self:T2(ARTY.id..string.format("%s: CombatReady or Firing+Timed Target ==> OpenFire", Controllable:GetName())) + elseif _timedTarget then - -- Engage target. - if _timedTarget then - - -- Cease fire on current target first. - if self.currentTarget then - self:CeaseFire(self.currentTarget) - end - - -- Open fire on timed target. - self:OpenFire(_timedTarget) - - elseif _normalTarget then - - -- Open fire on normal target. - self:OpenFire(_normalTarget) - + -- Cease fire on current target first. + if self.currentTarget then + self:CeaseFire(self.currentTarget) end + + -- Open fire on timed target. + self:OpenFire(_timedTarget) + + elseif _normalTarget then + + -- Open fire on normal target. + self:OpenFire(_normalTarget) + end + + -- Get ammo. + local nammo, nshells, nrockets, nmissiles=self:GetAmmo() + + -- Check if we have a target in the queue for which weapons are still available. + local gotsome=false + if #self.targets>0 then + for i=1,#self.targets do + local _target=self.targets[i] + if self:_CheckWeaponTypeAvailable(_target)>0 then + gotsome=true + end + end + else + -- No targets in the queue. + gotsome=true + end + + -- No ammo available. Either completely blank or only queued targets for ammo which is out. + if (nammo==0 or not gotsome) and not (self:is("Moving") or self:is("Rearming") or self:is("OutOfAmmo")) then + self:Winchester() + end + + -- Group is out of ammo. + if self:is("OutOfAmmo") then + self:T2(ARTY.id..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming", Controllable:GetName())) + self:Rearm() + end -- Call status again in ~10 sec. self:__Status(self.StatusInterval) @@ -1985,33 +2029,16 @@ function ARTY:onbeforeOpenFire(Controllable, From, Event, To, target) -- Deny transition. return false 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 + + -- Get the number of available shells, rockets or missiles requested for this target. + local nfire=self:_CheckWeaponTypeAvailable(target) -- 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 + local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.") return false end @@ -2237,8 +2264,7 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Route ARTY group to rearming place. if dA > self.RearmingDistance then local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2) - self:AssignMoveCoord(_tocoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "Relocate to rearming place", true) - --self:Move(, self.Speed, self.RearmingArtyOnRoad) + self:AssignMoveCoord(_tocoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE TO REARMING PLACE", true) end -- Route Rearming group to rearming place. @@ -2280,7 +2306,7 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Route ARTY group to rearming place. if dA > self.RearmingDistance then local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) - self:AssignMoveCoord(_tocoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "Relocate to rearming place", true) + self:AssignMoveCoord(_tocoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE TO REARMING PLACE", true) end end @@ -2307,10 +2333,9 @@ function ARTY:onafterRearmed(Controllable, From, Event, To) self.Nukes=self.Nukes0 -- Route ARTY group back to where it came from (if distance is > 100 m). - local d1=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) - if d1 > self.RearmingDistance then - --self:Move(self.InitialCoord, self.Speed, self.RearmingArtyOnRoad) - self:AssignMoveCoord(self.InitialCoord, nil, self.Speed, self.RearmingArtyOnRoad, false, "After rearm back to initial pos", true) + local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) + if dist > self.RearmingDistance then + self:AssignMoveCoord(self.InitialCoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE REARMING COMPLETE", true) end -- Route unit back to where it came from (if distance is > 100 m). @@ -2352,7 +2377,7 @@ function ARTY:_CheckRearmed() MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) end - -- Return if ammo is full. + -- 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. if nammo>=self.FullAmmo then return true else @@ -2369,20 +2394,27 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #table move Table containing the move parameters. -- @param Core.Point#COORDINATE ToCoord Coordinate to which the ARTY group should move. -- @param #boolean OnRoad If true group should move on road mainly. -- @return #boolean If true, proceed to onafterMove. -function ARTY:onbeforeMove(Controllable, From, Event, To, ToCoord, OnRoad) +function ARTY:onbeforeMove(Controllable, From, Event, To, move) self:_EventFromTo("onbeforeMove", Event, From, To) -- Check if group can actually move... - if self.SpeedMax==0 then + if self.SpeedMax<1 then return false end - -- Cease fire first. + -- Check if group is engaging. if self.currentTarget then - self:CeaseFire(self.currentTarget) + if move.cancel then + -- Cancel current target. + self:CeaseFire(self.currentTarget) + else + -- We should not cancel. + return false + end end return true @@ -2394,10 +2426,8 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Point#COORDINATE ToCoord Coordinate to which the ARTY group should move. --- @param #number Speed Speed in km/h at which the grou p should move. --- @param #boolean OnRoad If true group should move on road mainly. -function ARTY:onafterMove(Controllable, From, Event, To, ToCoord, Speed, OnRoad) +-- @param #table move Table containing the move parameters. +function ARTY:onafterMove(Controllable, From, Event, To, move) self:_EventFromTo("onafterMove", Event, From, To) -- Set alarm state to green and ROE to weapon hold. @@ -2405,15 +2435,18 @@ function ARTY:onafterMove(Controllable, From, Event, To, ToCoord, Speed, OnRoad) self.Controllable:OptionROEHoldFire() -- Take care of max speed. - local _Speed=math.min(Speed, self.SpeedMax) + local _Speed=math.min(move.speed, self.SpeedMax) -- Smoke coordinate if self.Debug then - ToCoord:SmokeRed() + move.coord:SmokeRed() end + + -- Set current move. + self.currentMove=move -- Route group to coodinate. - self:_Move(self.Controllable, ToCoord, _Speed, OnRoad) + self:_Move(self.Controllable, move.coord, move.speed, move.onroad) end @@ -2454,7 +2487,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #table target Array holding the target parameters. --- @return #boolean If true, proceed to onafterOpenfire. function ARTY:onafterNewTarget(Controllable, From, Event, To, target) self:_EventFromTo("onafterNewTarget", Event, From, To) @@ -2471,7 +2503,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #table move Array holding the move parameters. --- @return #boolean If true, proceed to onafterOpenfire. function ARTY:onafterNewMove(Controllable, From, Event, To, move) self:_EventFromTo("onafterNewTarget", Event, From, To) @@ -2688,6 +2719,7 @@ end -- @param #number Speed (Optional) Speed in km/h. Default is 70% of max speed the group can do. -- @param #boolean OnRoad If true, use (mainly) roads. function ARTY:_Move(group, ToCoord, Speed, OnRoad) + self:F2({group=group:GetName(), Speed=Speed, OnRoad=OnRoad}) -- Clear all tasks. group:ClearTasks() @@ -2722,28 +2754,31 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) -- Route group on road if requested. if OnRoad then - - -- Path on road (only first and last points) - --local _first=cpini:GetClosestPointToRoad() - --local _last=ToCoord:GetClosestPointToRoad() + -- Get path on road. local _pathonroad=cpini:GetPathOnRoad(ToCoord) - local _first=_pathonroad[1] - local _last=_pathonroad[#_pathonroad] + + -- Check if we actually got a path. There are situations where nil is returned. In that case, we go directly. + if _pathonroad then + + -- Just take the first and last point. + local _first=_pathonroad[1] + local _last=_pathonroad[#_pathonroad] - if self.Debug then - _first:SmokeBlue() - _last:SmokeBlue() + if self.Debug then + _first:SmokeGreen() + _last:SmokeGreen() + end + + -- First point on road. + path[#path+1]=_first:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + -- Last point on road. + path[#path+1]=_last:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) end - -- First point on road. - path[#path+1]=_first:WaypointGround(Speed, "On Road") - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) - - -- Last point on road. - path[#path+1]=_last:WaypointGround(Speed, "On Road") - task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) - end -- Last waypoint at ToCoord. @@ -2828,7 +2863,7 @@ function ARTY:_Relocate() -- Assign relocation. if _gotit then - self:AssignMoveCoord(_new, nil, nil, false, false) + self:AssignMoveCoord(_new, nil, nil, false, false, "RELOCATION MOVE AFTER FIRING") end end @@ -2901,30 +2936,13 @@ function ARTY:GetAmmo(display) MissileCategory=ammotable[w].desc.missileCategory end - local function missilecat(n) - local cat="unknown" - if n==1 then - cat="air-to-air" - elseif n==2 then - cat="surface-to-air" - elseif n==3 then - cat="ballistic" - elseif n==4 then - cat="anti-ship" - elseif n==5 then - cat="cruise" - elseif n==6 then - cat="other" - end - return cat - end -- Check for correct shell type. local _gotshell=false if #self.ammoshells>0 then -- User explicitly specified the valid type(s) of shells. for _,_type in pairs(self.ammoshells) do - if string.match(Tammo, _type) then + if string.match(Tammo, _type) and Category==Weapon.Category.SHELL then _gotshell=true end end @@ -2938,7 +2956,7 @@ function ARTY:GetAmmo(display) local _gotrocket=false if #self.ammorockets>0 then for _,_type in pairs(self.ammorockets) do - if string.match(Tammo, _type) then + if string.match(Tammo, _type) and Category==Weapon.Category.ROCKET then _gotrocket=true end end @@ -2952,7 +2970,7 @@ function ARTY:GetAmmo(display) local _gotmissile=false if #self.ammomissiles>0 then for _,_type in pairs(self.ammomissiles) do - if string.match(Tammo,_type) then + if string.match(Tammo,_type) and Category==Weapon.Category.MISSILE then _gotmissile=true end end @@ -2982,12 +3000,12 @@ function ARTY:GetAmmo(display) elseif _gotmissile then -- Add up all cruise missiles (category 5) - if MissileCategory==5 then + if MissileCategory==Weapon.MissileCategory.CRUISE then nmissiles=nmissiles+Nammo end -- Debug info. - text=text..string.format("- %d %s missiles of type %s\n", Nammo, missilecat(MissileCategory), Tammo) + text=text..string.format("- %d %s missiles of type %s\n", Nammo, self:_MissileCategoryName(MissileCategory), Tammo) else @@ -3016,6 +3034,29 @@ function ARTY:GetAmmo(display) return nammo, nshells, nrockets, nmissiles end +--- Returns a name of a missile category. +-- @param #ARTY self +-- @param #number categorynumber Number of missile category from weapon missile category enumerator. See https://wiki.hoggitworld.com/view/DCS_Class_Weapon +-- @return #string Missile category name. +function ARTY:_MissileCategoryName(categorynumber) + local cat="unknown" + if categorynumber==Weapon.MissileCategory.AAM then + cat="air-to-air" + elseif categorynumber==Weapon.MissileCategory.SAM then + cat="surface-to-air" + elseif categorynumber==Weapon.MissileCategory.BM then + cat="ballistic" + elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then + cat="anti-ship" + elseif categorynumber==Weapon.MissileCategory.CRUISE then + cat="cruise" + elseif categorynumber==Weapon.MissileCategory.OTHER then + cat="other" + end + return cat +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mark Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3111,6 +3152,7 @@ function ARTY:_Markertext(text) for _,key in pairs(keywords) do + -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma. local s=self:_split(key, " ") local val=s[2] @@ -3516,6 +3558,40 @@ function ARTY:_CheckTargetsInRange() end end +--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. +-- @param #ARTY self +-- @return #table Target which is due to be attacked now or nil if no target could be found. +function ARTY:_CheckNormalTargets() + self:F3() + + -- Sort targets w.r.t. prio and number times engaged already. + self:_SortTargetQueuePrio() + + -- No target engagements if rearming! + if self:is("Rearming") then + return nil + end + + -- Loop over all sorted targets. + for i=1,#self.targets do + local _target=self.targets[i] + + -- Debug info. + self:T3(ARTY.id..string.format("Check NORMAL target %d: %s", i, self:_TargetInfo(_target))) + + -- Check that target no time, is not under fire currently and in range. + if _target.underfire==false and _target.time==nil and _target.maxengage > _target.engaged and self:_TargetInRange(_target) and self:_CheckWeaponTypeAvailable(_target)>0 then + + -- Debug info. + self:T2(ARTY.id..string.format("Found NORMAL target %s", self:_TargetInfo(_target))) + + return _target + end + end + + return nil +end + --- Check all timed targets and return the target which should be attacked next. -- @param #ARTY self -- @return #table Target which is due to be attacked now. @@ -3528,6 +3604,11 @@ function ARTY:_CheckTimedTargets() -- Sort Targets wrt time. self:_SortQueueTime(self.targets) + -- No target engagements if rearming! + if self:is("Rearming") then + return nil + end + for i=1,#self.targets do local _target=self.targets[i] @@ -3535,7 +3616,7 @@ function ARTY:_CheckTimedTargets() self:T3(ARTY.id..string.format("Check TIMED target %d: %s", i, self:_TargetInfo(_target))) -- Check if target has an attack time which has already passed. Also check that target is not under fire already and that it is in range. - if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target) then + if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target) and self:_CheckWeaponTypeAvailable(_target)>0 then -- Check if group currently has a target and whether its priorty is lower than the timed target. if self.currentTarget then @@ -3550,6 +3631,7 @@ function ARTY:_CheckTimedTargets() return _target end end + end return nil @@ -3572,12 +3654,18 @@ function ARTY:_CheckMoves() if self.currentTarget then firing=true end - + + -- Loop over all moves in queue. for i=1,#self.moves do + + -- Shortcut. local _move=self.moves[i] - -- Check if time for move is reached. - if Tnow >= _move.time and (firing==false or _move.cancel) then + if string.find(_move.name, "REARMING MOVE") and ((self.currentMove and self.currentMove.name~=_move.name) or self.currentMove==nil) then + -- We got an rearming assignment which has priority. + return _move + elseif (Tnow >= _move.time) and (firing==false or _move.cancel) and (not self.currentMove) and (not self:is("Rearming")) then + -- Time for move is reached and maybe current target should be cancelled. return _move end end @@ -3585,35 +3673,6 @@ function ARTY:_CheckMoves() return nil end ---- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. --- @param #ARTY self --- @return #table Target which is due to be attacked now or nil if no target could be found. -function ARTY:_CheckNormalTargets() - self:F3() - - -- Sort targets w.r.t. prio and number times engaged already. - self:_SortTargetQueuePrio() - - -- Loop over all sorted targets. - for i=1,#self.targets do - local _target=self.targets[i] - - -- Debug info. - self:T3(ARTY.id..string.format("Check NORMAL target %d: %s", i, self:_TargetInfo(_target))) - - -- Check that target no time, is not under fire currently and in range. - if _target.underfire==false and _target.time==nil and _target.maxengage > _target.engaged and self:_TargetInRange(_target) then - - -- Debug info. - self:T2(ARTY.id..string.format("Found NORMAL target %s", self:_TargetInfo(_target))) - - return _target - end - end - - return nil -end - --- Check whether shooting started within a certain time (~5 min). If not, the current target is considered invalid and removed from the target list. -- @param #ARTY self function ARTY:_CheckShootingStarted() @@ -3691,6 +3750,66 @@ function ARTY:_GetMoveIndexByName(name) return nil 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. +-- @param #ARTY self +-- @param #boolean target Target array data structure. +-- @return #number Amount of shells, rockets or missiles available of the weapon type selected for the target. +function ARTY:_CheckWeaponTypeAvailable(target) + + -- Get current ammo of group. + local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() + + -- Check if enough ammo is there for the selected weapon type. + 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 + + return nfire +end +--- Check if a selected weapon type is in principle possible for this group. The current amount of ammo might be zero but the group still can be rearmed at a later point in time. +-- @param #ARTY self +-- @param #boolean target Target array data structure. +-- @return #boolean True if the group can carry this weapon type, false otherwise. +function ARTY:_CheckWeaponTypePossible(target) + + -- Check if enough ammo is there for the selected weapon type. + local possible=false + if target.weapontype==ARTY.WeaponType.Auto then + possible=self.Nammo0>0 + elseif target.weapontype==ARTY.WeaponType.Cannon then + possible=self.Nshells0>0 + elseif target.weapontype==ARTY.WeaponType.TacticalNukes then + possible=self.Nukes0>0 + elseif target.weapontype==ARTY.WeaponType.Rockets then + 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 + possible=self.Nmissiles0>0 + elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then + possible=self.Nmissiles0>0 + end + + return possible +end + --- Check if a name is unique. If not, a new unique name can be created by adding a running index #01, #02, ... -- @param #ARTY self -- @param #table givennames Table with entries of already given names. Must contain a .name item. From e8ff1534273d35d9f8689af8d4c53339d288476b Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 10 Jun 2018 00:05:55 +0200 Subject: [PATCH 14/16] ARTY v0.9.96 Improved marker logic. --- .../Moose/Functional/Artillery.lua | 366 +++++++++--------- 1 file changed, 191 insertions(+), 175 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index aa9a043c2..27e1a2d6a 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -55,7 +55,6 @@ -- @field #number Nrockets0 Initial amount of rockets 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 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 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. @@ -63,6 +62,8 @@ -- @field #string DisplayName Extended type name of 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 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 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. @@ -221,8 +222,6 @@ -- * @{#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}.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}.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. -- * *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. +-- * *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. -- -- 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 MRLS 1", key 666 -- 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 -- 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. -- * *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. +-- * *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. -- -- Here are some examples: @@ -323,18 +327,9 @@ -- arty move, time 23:45, speed 50, on road -- arty move, battery "Blue Paladin" -- arty move, battery "Blue MRLS", canceltarget, speed 10, on road +-- arty move, lldms 41:51:00N 41:47:58E -- --- ### Coordinate Independent Commands --- --- 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 +-- ### 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 -- @@ -347,6 +342,17 @@ -- arty request, battery "Paladin Bravo", targets -- 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 -- @@ -444,13 +450,13 @@ ARTY={ Nrockets0=0, Nmissiles0=0, Nukes0=0, - FullAmmo=0, - defaultROE="weapon_hold", StatusInterval=10, WaitForShotTime=300, DCSdesc=nil, Type=nil, DisplayName=nil, + alias=nil, + ismobile=true, IniGroupStrength=0, IsArtillery=nil, RearmingDistance=100, @@ -490,10 +496,10 @@ ARTY.WeaponType={ Auto=1073741822, Cannon=805306368, Rockets=30720, - UnguidedAny=805339120, - GuidedMissile=268402688, + --UnguidedAny=805339120, + --GuidedMissile=268402688, CruiseMissile=2097152, - AntiShipMissile=65536, + --AntiShipMissile=65536, TacticalNukes=666, } @@ -563,7 +569,7 @@ ARTY.id="ARTY | " --- Arty script 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. -- @param #ARTY self -- @param Wrapper.Group#GROUP group The GROUP object for which artillery tasks should be assigned. --- @return #ARTY ARTY object. --- @return nil If group does not exist or is not a ground or naval group. -function ARTY:New(group) +-- @param alias (Optional) Alias name the group will be calling itself when sending messages. +-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group. +function ARTY:New(group, alias) BASE:F2(group) -- Inherits from FSM_CONTROLLABLE @@ -616,11 +622,17 @@ function ARTY:New(group) if group:IsGround()==false and group:IsShip()==false then self:E(ARTY.id..string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!", group:GetName())) return nil - end + end -- Set the controllable for the FSM. 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. self.InitialCoord=group:GetCoordinate() @@ -628,9 +640,7 @@ function ARTY:New(group) local DCSgroup=Group.getByName(group:GetName()) local DCSunit=DCSgroup:getUnit(1) self.DCSdesc=DCSunit:getDesc() - - --self.DCSdesc=group:GetDesc() - + -- DCS descriptors. self:T3(ARTY.id.."DCS descriptors for group "..group:GetName()) for id,desc in pairs(self.DCSdesc) do @@ -640,6 +650,13 @@ function ARTY:New(group) -- Maximum speed in km/h. 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. self.Speed=self.SpeedMax * 0.7 @@ -790,6 +807,12 @@ end -- @return #string Name of the move. Can be used for further reference, e.g. deleting the move from the list. 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}) + + -- 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 if unique==nil then @@ -1165,6 +1188,13 @@ function ARTY:onafterStart(Controllable, From, Event, To) 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") text=text..string.format("Arty group = %s\n", Controllable:GetName()) 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("Speed max = %d km/h\n", self.SpeedMax) 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("Max range = %.1f km\n", self.maxrange/1000) text=text..string.format("Total ammo count = %d\n", self.Nammo0) @@ -1358,7 +1389,7 @@ function ARTY:_OnEventShot(EventData) self.Nshots=self.Nshots+1 -- 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) 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 uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype)) - + -- Default switches for cease fire and relocation. local _ceasefire=false local _relocate=false @@ -1474,66 +1505,6 @@ function ARTY:_OnEventShot(EventData) 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. -- @param #ARTY self @@ -1670,7 +1641,7 @@ function ARTY:_OnEventMarkChange(Event) local _assign=self:_Markertext(Event.text) -- 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 end @@ -1716,13 +1687,20 @@ function ARTY:_OnEventMarkChange(Event) end -- Cancel current target and return. - if _assign.cancelcurrent and _validkey then - if _assign.move and self.currentMove then + if _assign.cancel and _validkey then + if _assign.cancelmove and self.currentMove then self.Controllable:ClearTasks() self:Arrived() - end - if _assign.engage and self.currentTarget then + elseif _assign.canceltarget and self.currentTarget then + self.currentTarget.engaged=self.currentTarget.engaged+1 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 return end @@ -1761,7 +1739,7 @@ function ARTY:_OnEventMarkChange(Event) MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) -- 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 local _mid=self:_GetMoveIndexByName(_movename) @@ -2087,18 +2065,9 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) elseif target.weapontype==ARTY.WeaponType.Rockets then nfire=Nrockets _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 nfire=Nmissiles _type="cruise missiles" - elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then - nfire=Nmissiles - _type="anti-ship missiles" end -- Adjust if less than requested ammo is left. @@ -2165,6 +2134,8 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) -- Clear tasks. self.Controllable:ClearTasks() + else + self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.Controllable:GetName()) end -- Set number of shots to zero. @@ -2365,20 +2336,21 @@ function ARTY:_CheckRearmed() end -- Full Ammo count. - self.FullAmmo=self.Nammo0 * nunits / self.IniGroupStrength + local FullAmmo=self.Nammo0 * nunits / self.IniGroupStrength -- Rearming status in per cent. - local _rearmpc=nammo/self.FullAmmo*100 + local _rearmpc=nammo/FullAmmo*100 -- Send message if rearming > 1% complete 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) MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) 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. - if nammo>=self.FullAmmo then + -- Return if ammo is full. + -- 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 else return false @@ -2402,7 +2374,7 @@ function ARTY:onbeforeMove(Controllable, From, Event, To, move) self:_EventFromTo("onbeforeMove", Event, From, To) -- Check if group can actually move... - if self.SpeedMax<1 then + if not self.ismobile then return false end @@ -2528,10 +2500,7 @@ function ARTY:onafterDead(Controllable, From, Event, To) if units~=nil then nunits=#units end - - -- Adjust full ammo count - self.FullAmmo=self.Nammo0*nunits/self.IniGroupStrength - + -- Message. 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) @@ -2785,10 +2754,10 @@ function ARTY:_Move(group, ToCoord, Speed, OnRoad) path[#path+1]=ToCoord:WaypointGround(Speed, formation) task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) - if self.Debug then - cpini:SmokeBlue() - ToCoord:SmokeBlue() - end + --if self.Debug then + -- cpini:SmokeBlue() + -- ToCoord:SmokeBlue() + --end -- Init waypoints of the group. local Waypoints={} @@ -2927,6 +2896,9 @@ function ARTY:GetAmmo(display) -- Typename of current weapon 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 local Category=ammotable[w].desc.category @@ -2987,7 +2959,7 @@ function ARTY:GetAmmo(display) nshells=nshells+Nammo -- 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 @@ -2995,7 +2967,7 @@ function ARTY:GetAmmo(display) nrockets=nrockets+Nammo -- 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 @@ -3005,7 +2977,7 @@ function ARTY:GetAmmo(display) end -- 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 @@ -3130,9 +3102,12 @@ function ARTY:_Markertext(text) assignment.move=false assignment.engage=false assignment.request=false + assignment.cancel=false assignment.readonly=false + assignment.movecanceltarget=false + assignment.cancelmove=false assignment.canceltarget=false - assignment.cancelcurrent=false + assignment.cancelrearm=false -- Check for correct keywords. if text:lower():find("arty engage") or text:lower():find("arty attack") then @@ -3140,9 +3115,11 @@ function ARTY:_Markertext(text) elseif text:lower():find("arty move") or text:lower():find("arty relocate") then assignment.move=true elseif text:lower():find("arty request") then - assignment.request=true + assignment.request=true + elseif text:lower():find("arty cancel") then + assignment.cancel=true 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 end @@ -3150,11 +3127,12 @@ function ARTY:_Markertext(text) local keywords=self:_split(text, ",") 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. - local s=self:_split(key, " ") - local val=s[2] + local str=self:_split(keyphrase, " ") + local key=str[1] + local val=str[2] -- Battery name, i.e. which ARTY group should fire. if key:lower():find("battery") then @@ -3177,7 +3155,7 @@ function ARTY:_Markertext(text) 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)) elseif assignment.engage and key:lower():find("prio") then @@ -3202,7 +3180,7 @@ function ARTY:_Markertext(text) elseif val:lower():find("rocket") then assignment.weapontype=ARTY.WeaponType.Rockets elseif val:lower():find("missile") then - assignment.weapontype=ARTY.WeaponType.GuidedMissile + assignment.weapontype=ARTY.WeaponType.CruiseMissile elseif val:lower():find("nuke") then assignment.weapontype=ARTY.WeaponType.TacticalNukes else @@ -3225,16 +3203,11 @@ function ARTY:_Markertext(text) assignment.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.")) - 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 assignment.requestrearming=true @@ -3259,20 +3232,35 @@ function ARTY:_Markertext(text) assignment.requestmoves=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 local _flat = "%d+:%d+:%d+%s*[N,S]" local _flon = "%d+:%d+:%d+%s*[W,E]" - local _lat=key:match(_flat) - local _lon=key:match(_flon) - self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s", _lat,_lon)) + local _lat=keyphrase:match(_flat) + local _lon=keyphrase:match(_flon) + self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s format=DMS", _lat,_lon)) if _lat and _lon then -- Convert DMS string to DD numbers format. 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. if _latitude and _longitude then @@ -3464,11 +3452,11 @@ function ARTY:_CheckTargetsInRange() for i=1,#self.targets do 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. 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. local _movetowards=false @@ -3502,7 +3490,7 @@ function ARTY:_CheckTargetsInRange() if _inrange then -- 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) MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) end @@ -3528,15 +3516,15 @@ function ARTY:_CheckTargetsInRange() local _waytogo=_dist-self.maxrange+_safetymargin local _heading=self:_GetHeading(_from,_target.coord) _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 - -- Target was in range on previous check but now we are too far away. - local _waytogo=_dist-self.minrange+_safetymargin - local _heading=self:_GetHeading(_target.coord,_from) - _tocoord=_from:Translate(_waytogo, _heading) - _name=string.format("Relocation to within min firing range of target %s", _target.name) + -- Target was in range on previous check but now we are too far away. + local _waytogo=_dist-self.minrange+_safetymargin + local _heading=self:_GetHeading(_target.coord,_from) + _tocoord=_from:Translate(_waytogo, _heading) + _name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name) end @@ -3553,7 +3541,7 @@ function ARTY:_CheckTargetsInRange() -- Update value. _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 @@ -3750,6 +3738,52 @@ function ARTY:_GetMoveIndexByName(name) return nil 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. -- @param #ARTY self -- @param #boolean target Target array data structure. @@ -3769,14 +3803,8 @@ function ARTY:_CheckWeaponTypeAvailable(target) 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 return nfire @@ -3797,14 +3825,8 @@ function ARTY:_CheckWeaponTypePossible(target) possible=self.Nukes0>0 elseif target.weapontype==ARTY.WeaponType.Rockets then 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 possible=self.Nmissiles0>0 - elseif target.weapontype==ARTY.WeaponType.AntiShipMissile then - possible=self.Nmissiles0>0 end return possible @@ -3904,11 +3926,11 @@ function ARTY:_TargetInRange(target, message) if _dist < self.minrange then _inrange=false _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 _inrange=false _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 -- Debug output. @@ -3918,7 +3940,7 @@ function ARTY:_TargetInRange(target, message) end -- 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) end @@ -3938,14 +3960,8 @@ function ARTY:_WeaponTypeName(tnumber) name="Cannons" elseif tnumber==ARTY.WeaponType.Rockets then name="Rockets" - elseif tnumber==ARTY.WeaponType.UnguidedAny then - name="Unguided Weapons" -- (Cannon or Rockets) elseif tnumber==ARTY.WeaponType.CruiseMissile then 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 name="Tactical Nukes" end @@ -4039,7 +4055,7 @@ function ARTY:_LLDMS2DD(l1,l2) local _format = "%d+:%d+:%d+" local _ldms=ll:match(_format) - if ldms then + if _ldms then -- Split DMS to degrees, minutes and seconds. local _dms=self:_split(_ldms, ":") @@ -4071,8 +4087,8 @@ function ARTY:_LLDMS2DD(l1,l2) end -- Debug text. - local text=string.format("\nLatitude %.3f", _latitude) - text=text..string.format("\nLongitude %.3f", _longitude) + local text=string.format("\nLatitude %s", tostring(_latitude)) + text=text..string.format("\nLongitude %s", tostring(_longitude)) self:T2(ARTY.id..text) return _latitude,_longitude From ba2d359af2cc39d454611747f7b4d5a96ce98eed Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 10 Jun 2018 23:35:22 +0200 Subject: [PATCH 15/16] ARTY 1.0.0 - Added pseudo functions for FMS states. - Fixed a few bugs. --- Moose Development/Moose/Core/Event.lua | 5 +- .../Moose/Functional/Artillery.lua | 478 ++++++++++++++---- 2 files changed, 393 insertions(+), 90 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 4592d634e..a1be00587 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -933,7 +933,9 @@ function EVENT:onEvent( Event ) Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end - + +-- @FC: something like this should be added. +--[[ if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -942,6 +944,7 @@ function EVENT:onEvent( Event ) Event.MarkCoalition=Event.coalition Event.MarkGroupID = Event.groupID end +]] if Event.cargo then Event.Cargo = Event.cargo diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 27e1a2d6a..61201a6bf 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -63,12 +63,13 @@ -- @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 ismobile If true, ARTY group can move. +-- @field #string groupname Name of the ARTY group as defined in the mission editor. -- @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 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 Wrapper.Group#GROUP RearmingGroup Unit designated to rearm the ARTY group. --- @field #number RearmingGroupSpeed Speed in km/h the rearming unit moves at. Default 50 km/h. +-- @field #number RearmingGroupSpeed Speed in km/h the rearming unit moves at. Default is 50% of the max speed possible of the group. -- @field #boolean RearmingGroupOnRoad If true, rearming group will move to ARTY group or rearming place using mainly roads. Default false. -- @field Core.Point#COORDINATE RearmingGroupCoord Initial coordinates of the rearming unit. After rearming complete, the unit will return to this position. -- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there. @@ -358,15 +359,15 @@ -- -- The mission designer has a few options to tailor the ARTY object according to his needs. -- --- * @{#ARTY.SetAutomaticRelocate}(*maxdist*, *onroad*) lets the ARTY group automatically move to within firing range if a current target is outside the min/max firing range. The +-- * @{#ARTY.SetAutoRelocateToFiringRange}(*maxdist*, *onroad*) lets the ARTY group automatically move to within firing range if a current target is outside the min/max firing range. The -- optional parameter *maxdist* is the maximum distance im km the group will move. If the distance is greater no relocation is performed. Default is 50 km. --- * @{#ARTY.SetRelocateAfterEngagement}(*rmax*, *rmin*) will cause the ARTY group to change its position after each firing assignment. +-- * @{#ARTY.SetAutoRelocateAfterEngagement}(*rmax*, *rmin*) will cause the ARTY group to change its position after each firing assignment. -- Optional parameters *rmax*, *rmin* define the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. -- * @{#ARTY.RemoveAllTargets}() removes all targets from the target queue. -- * @{#ARTY.RemoveTarget}(*name*) deletes the target with *name* from the target queue. -- * @{#ARTY.SetMaxFiringRange}(*range*) defines the maximum firing range. Targets further away than this distance are not engaged. -- * @{#ARTY.SetMinFiringRange}(*range*) defines the minimum firing range. Targets closer than this distance are not engaged. --- * @{#ARTY.SetRearmingGroup}(*group*) sets the group resposible for rearming of the ARTY group once it is out of ammo. +-- * @{#ARTY.SetRearmingGroup}(*group*) sets the group responsible for rearming of the ARTY group once it is out of ammo. -- * @{#ARTY.SetReportON}() and @{#ARTY.SetReportOFF}() can be used to enable/disable status reports of the ARTY group send to all coalition members. -- * @{#ARTY.SetWaitForShotTime}(*waittime*) sets the time after which a target is deleted from the queue if no shooting event occured after the target engagement started. -- Default is 300 seconds. Note that this can for example happen, when the assigned target is out of range. @@ -455,13 +456,14 @@ ARTY={ DCSdesc=nil, Type=nil, DisplayName=nil, + groupname=nil, alias=nil, ismobile=true, IniGroupStrength=0, IsArtillery=nil, RearmingDistance=100, RearmingGroup=nil, - RearmingGroupSpeed=50, + RearmingGroupSpeed=nil, RearmingGroupOnRoad=false, RearmingGroupCoord=nil, RearmingPlaceCoord=nil, @@ -569,7 +571,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="0.9.96" +ARTY.version="1.0.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -586,18 +588,19 @@ ARTY.version="0.9.96" -- DONE: Abort firing task if no shooting event occured with 5(?) minutes. Something went wrong then. Min/max range for example. -- DONE: Improve assigned time for engagement. Next day? -- DONE: Improve documentation. --- TODO: Add pseudo user transitions. OnAfter... +-- DONE: Add pseudo user transitions. OnAfter... -- DONE: Make reaming unit a group. -- DONE: Write documenation. -- DONE: Add command move to make arty group move. -- DONE: remove schedulers for status event. --- TODO: Improve handling of special weapons. When winchester if using selected weapons? --- TODO: Handle rearming for ships. --- TODO: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. +-- DONE: Improve handling of special weapons. When winchester if using selected weapons? +-- TODO: Handle rearming for ships. How? +-- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. -- TODO: Add set commands via markers. E.g. set rearming place. --- TODO: Test stationary types like mortas ==> rearming etc. +-- DONE: Test stationary types like mortas ==> rearming etc. +-- TODO: Add hit event and make the arty group relocate. -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Creates a new ARTY object. -- @param #ARTY self @@ -627,10 +630,14 @@ function ARTY:New(group, alias) -- Set the controllable for the FSM. self:SetControllable(group) + -- Set the group name + self.groupname=group:GetName() + + -- Set an alias name. if alias~=nil then self.alias=tostring(alias) else - self.alias=group:GetName() + self.alias=self.groupname end -- Set the initial coordinates of the ARTY group. @@ -706,6 +713,268 @@ function ARTY:New(group, alias) -- Unknown transitons. To be checked if adding these causes problems. self:AddTransition("Rearming", "Arrived", "Rearming") self:AddTransition("Rearming", "Move", "Rearming") + + + --- User function for OnAfter "NewTarget" event. + -- @function [parent=#ARTY] OnAfterNewTarget + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfter "OpenFire" event. + -- @function [parent=#ARTY] OnAfterOpenFire + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfter "CeaseFire" event. + -- @function [parent=#ARTY] OnAfterCeaseFire + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfer "NewMove" event. + -- @function [parent=#ARTY] OnAfterNewMove + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table move Array holding the move info. + + --- User function for OnAfer "Move" event. + -- @function [parent=#ARTY] OnAfterMove + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table move Array holding the move info. + + --- User function for OnAfer "Arrived" event. + -- @function [parent=#ARTY] OnAfterArrvied + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Winchester" event. + -- @function [parent=#ARTY] OnAfterWinchester + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Rearm" event. + -- @function [parent=#ARTY] OnAfterRearm + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Rearmed" event. + -- @function [parent=#ARTY] OnAfterRearmed + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Start" event. + -- @function [parent=#ARTY] OnAfterStart + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Status" event. + -- @function [parent=#ARTY] OnAfterStatus + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Dead" event. + -- @function [parent=#ARTY] OnAfterDead + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- User function for OnEnter "CombatReady" state. + -- @function [parent=#ARTY] OnEnterCombatReady + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Firing" state. + -- @function [parent=#ARTY] OnEnterFiring + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "OutOfAmmo" state. + -- @function [parent=#ARTY] OnEnterOutOfAmmo + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Rearming" state. + -- @function [parent=#ARTY] OnEnterRearming + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Rearmed" state. + -- @function [parent=#ARTY] OnEnterRearmed + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Moving" state. + -- @function [parent=#ARTY] OnEnterMoving + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Function to start the ARTY FSM process. + -- @function [parent=#ARTY] Start + -- @param #ARTY self + + --- Function to start the ARTY FSM process after a delay. + -- @function [parent=#ARTY] __Start + -- @param #ARTY self + -- @param #number Delay before start in seconds. + + --- Function to update the status of the ARTY group and tigger FSM events. Triggers the FSM event "Status". + -- @function [parent=#ARTY] Status + -- @param #ARTY self + + --- Function to update the status of the ARTY group and tigger FSM events after a delay. Triggers the FSM event "Status". + -- @function [parent=#ARTY] __Status + -- @param #ARTY self + -- @param #number Delay in seconds. + + --- Function called when a unit of the ARTY group died. Triggers the FSM event "Dead". + -- @function [parent=#ARTY] Dead + -- @param #ARTY self + + --- Function called when a unit of the ARTY group died after a delay. Triggers the FSM event "Dead". + -- @function [parent=#ARTY] __Dead + -- @param #ARTY self + -- @param #number Delay in seconds. + + --- Add a new target for the ARTY group. Triggers the FSM event "NewTarget". + -- @function [parent=#ARTY] NewTarget + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Add a new target for the ARTY group with a delay. Triggers the FSM event "NewTarget". + -- @function [parent=#ARTY] __NewTarget + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Add a new relocation move for the ARTY group. Triggers the FSM event "NewMove". + -- @function [parent=#ARTY] NewMove + -- @param #ARTY self + -- @param #table move Array holding the relocation move data. + + --- Add a new relocation for the ARTY group after a delay. Triggers the FSM event "NewMove". + -- @function [parent=#ARTY] __NewMove + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table move Array holding the relocation move data. + + --- Order ARTY group to open fire on a target. Triggers the FSM event "OpenFire". + -- @function [parent=#ARTY] OpenFire + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Order ARTY group to open fire on a target with a delay. Triggers the FSM event "Move". + -- @function [parent=#ARTY] __OpenFire + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Order ARTY group to cease firing on a target. Triggers the FSM event "CeaseFire". + -- @function [parent=#ARTY] CeaseFire + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Order ARTY group to cease firing on a target after a delay. Triggers the FSM event "CeaseFire". + -- @function [parent=#ARTY] __CeaseFire + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Order ARTY group to move to another location. Triggers the FSM event "Move". + -- @function [parent=#ARTY] Move + -- @param #ARTY self + -- @param #table move Array holding the relocation move data. + + --- Order ARTY group to move to another location after a delay. Triggers the FSM event "Move". + -- @function [parent=#ARTY] __Move + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table move Array holding the relocation move data. + + --- Tell ARTY group it has arrived at its destination. Triggers the FSM event "Arrived". + -- @function [parent=#ARTY] Arrived + -- @param #ARTY self + + --- Tell ARTY group it has arrived at its destination after a delay. Triggers the FSM event "Arrived". + -- @function [parent=#ARTY] __Arrived + -- @param #ARTY self + -- @param #number delay Delay in seconds. + + --- Tell ARTY group it is combat ready. Triggers the FSM event "CombatReady". + -- @function [parent=#ARTY] CombatReady + -- @param #ARTY self + + --- Tell ARTY group it is combat ready after a delay. Triggers the FSM event "CombatReady". + -- @function [parent=#ARTY] __CombatReady + -- @param #ARTY self + -- @param #number delay Delay in seconds. + + --- Tell ARTY group it is out of ammo. Triggers the FSM event "Winchester". + -- @function [parent=#ARTY] Winchester + -- @param #ARTY self + + --- Tell ARTY group it is out of ammo after a delay. Triggers the FSM event "Winchester". + -- @function [parent=#ARTY] __Winchester + -- @param #ARTY self + -- @param #number delay Delay in seconds. + return self end @@ -776,7 +1045,7 @@ function ARTY:AssignTargetCoord(coord, prio, radius, nshells, maxengage, time, w -- Target name should be unique and is not. if unique==true and _unique==false then - self:T(ARTY.id..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!", self.Controllable:GetName(), _name)) + self:T(ARTY.id..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!", self.groupname, _name)) return nil end @@ -810,7 +1079,7 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, 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())) + self:T(ARTY.id..string.format("%s: group is immobile. Rejecting move request!", self.groupname)) return nil end @@ -828,7 +1097,7 @@ function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) -- Move name should be unique and is not. if unique==true and _unique==false then - self:T(ARTY.id..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!", self.Controllable:GetName(), _name)) + self:T(ARTY.id..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!", self.groupname, _name)) return nil end @@ -909,7 +1178,7 @@ end --- Assign a group, which is responsible for rearming the ARTY group. If the group is too far away from the ARTY group it will be guided towards the ARTY group. -- @param #ARTY self --- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. +-- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. For the blue coalition, this is often a unarmed M818 transport whilst for red an unarmed Ural-375 transport can be used. function ARTY:SetRearmingGroup(group) self:F({group=group}) self.RearmingGroup=group @@ -917,10 +1186,10 @@ end --- Set the speed the rearming group moves at towards the ARTY group or the rearming place. -- @param #ARTY self --- @param #number speed Speed in km/h. Default 50 km/h. +-- @param #number speed Speed in km/h. function ARTY:SetRearmingGroupSpeed(speed) self:F({speed=speed}) - self.RearmingGroupSpeed=speed or 50 + self.RearmingGroupSpeed=speed end --- Define if rearming group uses mainly roads to drive to the ARTY group or rearming place. @@ -957,7 +1226,7 @@ end -- @param #ARTY self -- @param #number maxdistance (Optional) The maximum distance in km the group will travel to get within firing range. Default is 50 km. No automatic relocation is performed if targets are assigned which are further away. -- @param #boolean onroad (Optional) If true, ARTY group uses roads whenever possible. Default false, i.e. group will move in a straight line to the assigned coordinate. -function ARTY:SetAutomaticRelocate(maxdistance, onroad) +function ARTY:SetAutoRelocateToFiringRange(maxdistance, onroad) self:F({distance=maxdistance, onroad=onroad}) self.autorelocate=true self.autorelocatemaxdist=maxdistance or 50 @@ -968,6 +1237,19 @@ function ARTY:SetAutomaticRelocate(maxdistance, onroad) self.autorelocateonroad=onroad end +--- Set relocate after firing. Group will find a new location after each engagement. Default is off +-- @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:SetAutoRelocateAfterEngagement(rmax, rmin) + self.relocateafterfire=true + self.relocateRmax=rmax or 800 + self.relocateRmin=rmin or 300 + + -- Ensure that Rmin<=Rmax + self.relocateRmin=math.min(self.relocateRmin, self.relocateRmax) +end + --- Report messages of ARTY group turned on. This is the default. -- @param #ARTY self function ARTY:SetReportON() @@ -1004,19 +1286,19 @@ function ARTY:RemoveTarget(name) if id then -- Remove target from table. - self:T(ARTY.id..string.format("Group %s: Removing target %s (id=%d).", self.Controllable:GetName(), name, id)) + self:T(ARTY.id..string.format("Group %s: Removing target %s (id=%d).", self.groupname, name, id)) table.remove(self.targets, id) -- Delete marker belonging to this engagement. if self.markallow then local batteryname,markTargetID, markMoveID=self:_GetMarkIDfromName(name) - if batteryname==self.Controllable:GetName() and markTargetID~=nil then + if batteryname==self.groupname and markTargetID~=nil then COORDINATE:RemoveMark(markTargetID) end end end - self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.Controllable:GetName(), #self.targets)) + self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.groupname, #self.targets)) end --- Delete a move from move list. @@ -1031,19 +1313,19 @@ function ARTY:RemoveMove(name) if id then -- Remove move from table. - self:T(ARTY.id..string.format("Group %s: Removing move %s (id=%d).", self.Controllable:GetName(), name, id)) + self:T(ARTY.id..string.format("Group %s: Removing move %s (id=%d).", self.groupname, name, id)) table.remove(self.moves, id) -- Delete marker belonging to this relocation move. if self.markallow then local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) - if batteryname==self.Controllable:GetName() and markMoveID~=nil then + if batteryname==self.groupname and markMoveID~=nil then COORDINATE:RemoveMark(markMoveID) end end end - self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.Controllable:GetName(), #self.moves)) + self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.groupname, #self.moves)) end --- Delete ALL targets from current target list. @@ -1114,16 +1396,6 @@ function ARTY:SetTacNukeFires(nfires, range) 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 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:SetRelocateAfterEngagement(rmax, rmin) - self.relocateafterfire=true - self.relocateRmax=rmax or 800 - self.relocateRmin=rmin or 300 -end - --- Enable assigning targets and moves by placing markers on the F10 map. -- @param #ARTY self -- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. @@ -1193,10 +1465,31 @@ function ARTY:onafterStart(Controllable, From, Event, To) self.RearmingPlaceCoord=nil self.relocateafterfire=false self.autorelocate=false + self.RearmingGroupSpeed=20 + end + + -- Set Rearming group speed if not specified by user + if self.RearmingGroup then + + -- Get max speed of rearming group. + local speedmax=self.RearmingGroup:GetSpeedMax() + self:T(ARTY.id..string.format("%s, rearming group %s max speed = %.1f km/h.", self.groupname, self.RearmingGroup:GetName(), speedmax)) + + if self.RearmingGroupSpeed==nil then + -- Set rearming group speed to 50% of max possible speed. + self.RearmingGroupSpeed=speedmax*0.5 + else + -- Ensure that speed is <= max speed. + self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed, self.RearmingGroup:GetSpeedMax()) + end + else + -- Just to have a reasonable number for output format below. + self.RearmingGroupSpeed=23 end local text=string.format("\n******************************************************\n") - text=text..string.format("Arty group = %s\n", Controllable:GetName()) + text=text..string.format("Arty group = %s\n", self.groupname) + text=text..string.format("Arty alias = %s\n", self.alias) text=text..string.format("Artillery attribute = %s\n", tostring(self.IsArtillery)) text=text..string.format("Type = %s\n", self.Type) text=text..string.format("Display Name = %s\n", self.DisplayName) @@ -1230,7 +1523,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Relocate after fire = %s\n", tostring(self.relocateafterfire)) text=text..string.format("Relocate min dist. = %d m\n", self.relocateRmin) text=text..string.format("Relocate max dist. = %d m\n", self.relocateRmax) - text=text..string.format("Auto move in range = %s\n", tostring(self.autorelocate)) + text=text..string.format("Auto move in range = %s\n", tostring(self.autorelocate)) text=text..string.format("Auto move dist. max = %.1f km\n", self.autorelocatemaxdist/1000) text=text..string.format("Auto move on road = %s\n", tostring(self.autorelocateonroad)) text=text..string.format("Marker assignments = %s\n", tostring(self.markallow)) @@ -1322,7 +1615,7 @@ function ARTY:_StatusReport(display) local Clock=self:_SecondsToClock(timer.getAbsTime()) local text=string.format("\n******************* STATUS ***************************\n") - text=text..string.format("ARTY group = %s\n", self.Controllable:GetName()) + text=text..string.format("ARTY group = %s\n", self.groupname) text=text..string.format("Clock = %s\n", Clock) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Total ammo count = %d\n", Nammo) @@ -1381,7 +1674,7 @@ function ARTY:_OnEventShot(EventData) if group and group:IsAlive() then - if EventData.IniGroupName == self.Controllable:GetName() then + if EventData.IniGroupName == self.groupname then if self.currentTarget then @@ -1406,7 +1699,7 @@ function ARTY:_OnEventShot(EventData) return _weapon:getPoint() end) - self:T(ARTY.id..string.format("ARTY %s: Weapon still in air: %s", self.Controllable:GetName(), tostring(_status))) + self:T(ARTY.id..string.format("ARTY %s: Weapon still in air: %s", self.groupname, tostring(_status))) if _status then @@ -1430,7 +1723,7 @@ function ARTY:_OnEventShot(EventData) -- 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())) + self:T(ARTY.id..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname)) timer.scheduleFunction(_TrackWeapon, EventData.weapon, timer.getTime() + 2.0) end @@ -1445,7 +1738,7 @@ function ARTY:_OnEventShot(EventData) -- Check if we are completely out of ammo. local _outofammo=false 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.groupname)) _outofammo=true end @@ -1455,8 +1748,8 @@ function ARTY:_OnEventShot(EventData) -- Weapon type name for current target. 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 uses weapontype %s for current target.", self.Controllable:GetName(), _weapontype)) + self:T(ARTY.id..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.groupname, _nammo, _nshells, _nrockets, _nmissiles)) + self:T(ARTY.id..string.format("Group %s uses weapontype %s for current target.", self.groupname, _weapontype)) -- Default switches for cease fire and relocation. local _ceasefire=false @@ -1466,7 +1759,7 @@ function ARTY:_OnEventShot(EventData) if self.Nshots >= self.currentTarget.nshells then -- Debug message - 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.groupname, self.currentTarget.name) self:T(ARTY.id..text) MESSAGE:New(text, 5):ToAllIf(self.Debug) @@ -1499,7 +1792,7 @@ function ARTY:_OnEventShot(EventData) end else - self:E(ARTY.id..string.format("WARNING: No current target for group %s?!", self.Controllable:GetName())) + self:E(ARTY.id..string.format("WARNING: No current target for group %s?!", self.groupname)) end end end @@ -1517,7 +1810,7 @@ function ARTY:onEvent(Event) end -- Set battery and coalition. - local batteryname=self.Controllable:GetName() + local batteryname=self.groupname local batterycoalition=self.Controllable:GetCoalition() self:T2(string.format("Event captured = %s", tostring(batteryname))) @@ -1557,7 +1850,7 @@ function ARTY:_OnEventMarkRemove(Event) -- Get battery coalition and name. local batterycoalition=self.Controllable:GetCoalition() - local batteryname=self.Controllable:GetName() + local batteryname=self.groupname if Event.text~=nil and Event.text:find("BATTERY") then @@ -1632,7 +1925,7 @@ function ARTY:_OnEventMarkChange(Event) -- Get battery coalition and name. local batterycoalition=self.Controllable:GetCoalition() - local batteryname=self.Controllable:GetName() + local batteryname=self.groupname -- Check if the coalition is the same or an authorization key has been defined. if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then @@ -1650,7 +1943,7 @@ function ARTY:_OnEventMarkChange(Event) if #_assign.battery>0 then _assigned=false for _,bat in pairs(_assign.battery) do - self:T2(ARTY.id..string.format("Compare battery names %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) + self:T3(ARTY.id..string.format("Compare battery names %s=%s ==> %s",batteryname, bat, tostring(batteryname==bat))) if batteryname==bat then _assigned=true end @@ -1694,7 +1987,7 @@ function ARTY:_OnEventMarkChange(Event) elseif _assign.canceltarget and self.currentTarget then self.currentTarget.engaged=self.currentTarget.engaged+1 self:CeaseFire(self.currentTarget) - elseif _assign.cancelrearm and self.is("Rearming") then + elseif _assign.cancelrearm and self:is("Rearming") then local nammo=self:GetAmmo() if nammo>0 then self:Rearmed() @@ -1770,7 +2063,7 @@ function ARTY:_OnEventMarkChange(Event) if _assign.prio then text=text..string.format("\nPrio %d",_assign.prio) end - if _assign.prio then + if _assign.radius then text=text..string.format("\nRadius %d m",_assign.radius) end if _assign.nshells then @@ -1816,7 +2109,7 @@ function ARTY:_OnEventDead(EventData) self:F(EventData) -- Name of controllable. - local _name=self.Controllable:GetName() + local _name=self.groupname -- Check for correct group. if EventData.IniGroupName==_name then @@ -1898,7 +2191,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) end end for _,targetname in pairs(notpossible) do - self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.Controllable:GetName(), targetname)) + self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.groupname, targetname)) self:RemoveTarget(targetname) end @@ -1995,7 +2288,7 @@ function ARTY:onbeforeOpenFire(Controllable, From, Event, To, target) -- Check that group has no current target already. if self.currentTarget then -- This should not happen. Some earlier check failed. - self:E(ARTY.id..string.format("ERROR: Group %s already has a target %s!", self.Controllable:GetName(), self.currentTarget.name)) + self:E(ARTY.id..string.format("ERROR: Group %s already has a target %s!", self.groupname, self.currentTarget.name)) -- Deny transition. return false end @@ -2003,7 +2296,7 @@ function ARTY:onbeforeOpenFire(Controllable, From, Event, To, target) -- Check if target is in range. if not self:_TargetInRange(target) then -- This should not happen. Some earlier check failed. - self:E(ARTY.id..string.format("ERROR: Group %s, target %s is out of range!", self.Controllable:GetName(), self.currentTarget.name)) + self:E(ARTY.id..string.format("ERROR: Group %s, target %s is out of range!", self.groupname, self.currentTarget.name)) -- Deny transition. return false end @@ -2135,7 +2428,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) self.Controllable:ClearTasks() else - self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.Controllable:GetName()) + self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.groupname) end -- Set number of shots to zero. @@ -2178,10 +2471,10 @@ function ARTY:onbeforeRearm(Controllable, From, Event, To) local _rearmed=self:_CheckRearmed() if _rearmed then - self:T(ARTY.id..string.format("%s, group is already armed to the teeth. Rearming request denied!", self.Controllable:GetName())) + self:T(ARTY.id..string.format("%s, group is already armed to the teeth. Rearming request denied!", self.groupname)) return false else - self:T(ARTY.id..string.format("%s, group might be rearmed.", self.Controllable:GetName())) + self:T(ARTY.id..string.format("%s, group might be rearmed.", self.groupname)) end -- Check if a reaming unit or rearming place was specified. @@ -2240,7 +2533,8 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Route Rearming group to rearming place. if dR > self.RearmingDistance then - self:_Move(self.RearmingGroup, self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2), self.RearmingGroupSpeed, self.RearmingGroupOnRoad) + local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2) + self:_Move(self.RearmingGroup, ToCoord, self.RearmingGroupSpeed, self.RearmingGroupOnRoad) end elseif self.RearmingGroup then @@ -2314,6 +2608,9 @@ function ARTY:onafterRearmed(Controllable, From, Event, To) local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord) if d > self.RearmingDistance then self:_Move(self.RearmingGroup, self.RearmingGroupCoord, self.RearmingGroupSpeed, self.RearmingGroupOnRoad) + else + -- Clear tasks. + self.RearmingGroup:ClearTasks() end end @@ -2502,7 +2799,7 @@ function ARTY:onafterDead(Controllable, From, Event, To) end -- 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.groupname, nunits) MESSAGE:New(text, 5):ToAllIf(self.Debug) self:T(ARTY.id..text) @@ -2800,7 +3097,7 @@ function ARTY._PassingWaypoint(group, arty, i, final) ]] -- Arrived event. - if final and arty.Controllable:GetName()==group:GetName() then + if final and arty.groupname==group:GetName() then arty:Arrived() end @@ -2868,7 +3165,7 @@ function ARTY:GetAmmo(display) if unit and unit:IsAlive() then -- Output. - local text=string.format("ARTY group %s - unit %s:\n", self.Controllable:GetName(), unit:GetName()) + local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName()) -- Get ammo table. local ammotable=unit:GetAmmo() @@ -3040,7 +3337,7 @@ end function ARTY:_MarkerKeyAuthentification(text) -- Set battery and coalition. - local batteryname=self.Controllable:GetName() + local batteryname=self.groupname local batterycoalition=self.Controllable:GetCoalition() -- Get assignment. @@ -3133,11 +3430,14 @@ function ARTY:_Markertext(text) local str=self:_split(keyphrase, " ") local key=str[1] local val=str[2] + + -- Debug output. + self:T3(ARTY.id..string.format("%s, keyphrase = %s, key = %s, val = %s", self.groupname, tostring(keyphrase), tostring(key), tostring(val))) -- Battery name, i.e. which ARTY group should fire. if key:lower():find("battery") then - local v=self:_split(key, '"') + local v=self:_split(keyphrase, '"') for i=2,#v,2 do table.insert(assignment.battery, v[i]) @@ -3193,57 +3493,57 @@ function ARTY:_Markertext(text) assignment.speed=tonumber(val) self:T2(ARTY.id..string.format("Key Speed=%s.", val)) - elseif assignment.move and (key:lower():find("on road") or key:lower():find("onroad") or key:lower():find("use road")) then + elseif assignment.move and (keyphrase:lower():find("on road") or keyphrase:lower():find("onroad") or keyphrase:lower():find("use road")) then assignment.onroad=true self:T2(ARTY.id..string.format("Key Onroad=true.")) - elseif key:lower():find("irrevocable") or key:lower():find("readonly") then + elseif keyphrase:lower():find("irrevocable") or keyphrase:lower():find("readonly") then assignment.readonly=true self:T2(ARTY.id..string.format("Key Readonly=true.")) - elseif assignment.move and (key:lower():find("cancel target") or key:lower():find("cancel target")) then + elseif assignment.move and (keyphrase:lower():find("cancel target") or keyphrase:lower():find("canceltarget")) then assignment.movecanceltarget=true self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) - elseif assignment.request and key:lower():find("rearm") then + elseif assignment.request and keyphrase:lower():find("rearm") then assignment.requestrearming=true self:T2(ARTY.id..string.format("Key Request Rearming=true.")) - elseif assignment.request and key:lower():find("ammo") then + elseif assignment.request and keyphrase:lower():find("ammo") then assignment.requestammo=true self:T2(ARTY.id..string.format("Key Request Ammo=true.")) - elseif assignment.request and key:lower():find("target") then + elseif assignment.request and keyphrase:lower():find("target") then assignment.requesttargets=true self:T2(ARTY.id..string.format("Key Request Targets=true.")) - elseif assignment.request and key:lower():find("status") then + elseif assignment.request and keyphrase:lower():find("status") then assignment.requeststatus=true self:T2(ARTY.id..string.format("Key Request Status=true.")) - elseif assignment.request and (key:lower():find("move") or key:lower():find("relocation")) then + elseif assignment.request and (keyphrase:lower():find("move") or keyphrase:lower():find("relocation")) then assignment.requestmoves=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 + elseif assignment.cancel and (keyphrase:lower():find("engagement") or keyphrase:lower():find("attack") or keyphrase: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 + elseif assignment.cancel and (keyphrase:lower():find("move") or keyphrase: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 + elseif assignment.cancel and keyphrase:lower():find("rearm") then assignment.cancelrearm=true self:T2(ARTY.id..string.format("Key Cancel Rearm=true.")) @@ -3289,7 +3589,7 @@ end --- Request Moves. -- @param #ARTY self function ARTY:_MarkRequestMoves() - local text=string.format("%s, relocations:", self.Controllable:GetName()) + local text=string.format("%s, relocations:", self.groupname) if #self.moves>0 then for _,move in pairs(self.moves) do if self.currentMove and move.name == self.currentMove.name then @@ -3307,7 +3607,7 @@ end --- Request Targets. -- @param #ARTY self function ARTY:_MarkRequestTargets() - local text=string.format("%s, targets:", self.Controllable:GetName()) + local text=string.format("%s, targets:", self.groupname) if #self.targets>0 then for _,target in pairs(self.targets) do if self.currentTarget and target.name == self.currentTarget.name then @@ -3327,7 +3627,7 @@ end -- @param #number markerid ID of the placed marker. -- @return #string Name of target engagement. function ARTY:_MarkTargetName(markerid) - return string.format("BATTERY=%s, Marked Target ID=%d", self.Controllable:GetName(), markerid) + return string.format("BATTERY=%s, Marked Target ID=%d", self.groupname, markerid) end --- Create a name for a relocation move initiated by placing a marker. @@ -3335,7 +3635,7 @@ end -- @param #number markerid ID of the placed marker. -- @return #string Name of relocation move. function ARTY:_MarkMoveName(markerid) - return string.format("BATTERY=%s, Marked Relocation ID=%d", self.Controllable:GetName(), markerid) + return string.format("BATTERY=%s, Marked Relocation ID=%d", self.groupname, markerid) end --- Get the marker ID from the assigned task name. @@ -3679,14 +3979,14 @@ function ARTY:_CheckShootingStarted() -- Debug info if self.Nshots==0 then - self:T(ARTY.id..string.format("%s, waiting for %d seconds for first shot on target %s.", self.Controllable:GetName(), dt, name)) + self:T(ARTY.id..string.format("%s, waiting for %d seconds for first shot on target %s.", self.groupname, dt, name)) end -- Check if we waited long enough and no shot was fired. if dt > self.WaitForShotTime and self.Nshots==0 then -- Debug info. - self:T(ARTY.id..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.Controllable:GetName(), self.WaitForShotTime, name)) + self:T(ARTY.id..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name)) -- CeaseFire. self:CeaseFire(self.currentTarget) @@ -3754,27 +4054,27 @@ function ARTY:_CheckOutOfAmmo(targets) 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)) + self:T(ARTY.id..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.", self.groupname, 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)) + self:T(ARTY.id..string.format("Group %s, cannons requested for target %s but shells empty.", self.groupname, 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)) + self:T(ARTY.id..string.format("Group %s, tactical nukes requested for target %s but nukes empty.", self.groupname, 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)) + self:T(ARTY.id..string.format("Group %s, rockets requested for target %s but rockets empty.", self.groupname, 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)) + self:T(ARTY.id..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.", self.groupname, Target.name)) _partlyoutofammo=true end @@ -3994,7 +4294,7 @@ end -- @param #string From From state. -- @param #string To To state. function ARTY:_EventFromTo(BA, Event, From, To) - local text=string.format("%s: %s EVENT %s: %s --> %s", BA, self.Controllable:GetName(), Event, From, To) + local text=string.format("%s: %s EVENT %s: %s --> %s", BA, self.groupname, Event, From, To) self:T3(ARTY.id..text) end From d5d2de75776863ea5495196f7047912e36125773 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 10 Jun 2018 23:41:07 +0200 Subject: [PATCH 16/16] Controllable alarm state ships don't have option to change alarm state to other than green --- Moose Development/Moose/Wrapper/Controllable.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index d539dd098..0b56cc0d7 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2654,7 +2654,6 @@ function CONTROLLABLE:OptionAlarmStateGreen() elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) - Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) end return self