diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 435bec5a4..7d27618ab 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -329,6 +329,17 @@ function ASTAR:SetValidNeighbourDistance(MaxDistance) return self end +--- Set valid neighbours to be in a certain distance. +-- @param #ASTAR self +-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourRoad(MaxDistance) + + self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance) + + return self +end + --- Set the function which calculates the "cost" to go from one to another node. -- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments. -- Very often the distance between nodes is a good measure for the cost. @@ -368,6 +379,16 @@ function ASTAR:SetCostDist3D() return self end +--- Set heuristic cost to go from one node to another to be their 3D distance. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:SetCostRoad() + + self:SetCostFunction(ASTAR) + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Grid functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -510,6 +531,22 @@ function ASTAR.LoS(nodeA, nodeB, corridor) return los end +--- Function to check if two nodes have a road connection. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #boolean If true, two nodes are connected via a road. +function ASTAR.Road(nodeA, nodeB) + + local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z) + + if path then + return true + else + return false + end + +end + --- Function to check if distance between two nodes is less than a threshold distance. -- @param #ASTAR.Node nodeA First node. -- @param #ASTAR.Node nodeB Other node. @@ -533,7 +570,8 @@ end -- @param #ASTAR.Node nodeB Other node. -- @return #number Distance between the two nodes. function ASTAR.Dist2D(nodeA, nodeB) - return nodeA.coordinate:Get2DDistance(nodeB) + local dist=nodeA.coordinate:Get2DDistance(nodeB) + return dist end --- Heuristic cost is given by the 3D distance between the nodes. @@ -541,7 +579,8 @@ end -- @param #ASTAR.Node nodeB Other node. -- @return #number Distance between the two nodes. function ASTAR.Dist3D(nodeA, nodeB) - return nodeA.coordinate:Get3DDistance(nodeB.coordinate) + local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate) + return dist end --- Heuristic cost is given by the distance between the nodes on road. @@ -550,15 +589,26 @@ end -- @return #number Distance between the two nodes. function ASTAR.DistRoad(nodeA, nodeB) - local path, dist, gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate, IncludeEndpoints, Railroad, MarkPath, SmokePath) + -- Get the path. + local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z) - if gotpath then - return dist - else - return math.huge - end + if path then + + local dist=0 + + for i=2,#path do + local b=path[i] --DCS#Vec2 + local a=path[i-1] --DCS#Vec2 + + dist=dist+UTILS.VecDist2D(a,b) + + end - return nodeA.coordinate:Get3DDistance(nodeB.coordinate) + return dist + end + + + return math.huge end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 94197f066..c566c5b1c 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -167,14 +167,16 @@ end -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio) Waypoint=Waypoint or self:GetWaypointNext() local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) - self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio) + local task=self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio) + return task end --- Add a *scheduled* task. @@ -184,12 +186,14 @@ end -- @param #number WeaponType Type of weapon. Default auto. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio) local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack) - self:AddTask(DCStask, Clock, nil, Prio) + local task=self:AddTask(DCStask, Clock, nil, Prio) + return task end --- Check if the group is currently holding its positon. @@ -737,6 +741,10 @@ function ARMYGROUP:_InitGroup() -- Set default formation from first waypoint. self.option.Formation=self:GetWaypoint(1).action self.optionDefault.Formation=self.option.Formation + + -- Default TACAN off. + self:SetDefaultTACAN(nil, nil, nil, nil, true) + self.tacan=UTILS.DeepCopy(self.tacanDefault) -- Units of the group. local units=self.group:GetUnits() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index ed7cb5029..8487eef05 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -201,7 +201,7 @@ end -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task data. -function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio) +function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) @@ -462,22 +462,37 @@ function NAVYGROUP:onafterStatus(From, Event, To) local turning=tostring(self:IsTurning()) local alt=self.position.y local speed=UTILS.MpsToKnots(self.velocity) - local speedExpected=UTILS.MpsToKnots(self.speedWp or 0) + local speedExpected=UTILS.MpsToKnots(self:GetExpectedSpeed()) --UTILS.MpsToKnots(self.speedWp or 0) + -- Waypoint stuff. local wpidxCurr=self.currentwp - local wpuidCurr=0 - local wpidxNext=self:GetWaypointIndexNext() - local wpuidNext=0 - local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint()) - local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint(), true) + local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr) or 0 + local wpidxNext=self:GetWaypointIndexNext() or 0 + local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 + local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint() or 0) + local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint() or 0, true) + + -- Current ROE and alarm state. local roe=self:GetROE() or 0 local als=self:GetAlarmstate() or 0 -- Info text. local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", - fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) + fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints or 0, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) self:I(self.lid..text) + if false then + local text="Waypoints:" + for i,wp in pairs(self.waypoints) do + local waypoint=wp --Ops.OpsGroup#OPSGROUP.Waypoint + text=text..string.format("\n%d. UID=%d", i, waypoint.uid) + if i==self.currentwp then + text=text.." current!" + end + end + env.info(text) + end + end else @@ -584,7 +599,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) for i=n, #self.waypoints do -- Waypoint. - local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint + local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint -- Check if next wp. if i==n then @@ -594,7 +609,10 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Take speed specified. wp.speed=UTILS.KnotsToMps(Speed) else - -- Take default waypoint speed. + -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. + if self.adinfinitum and wp.speed<0.1 then + wp.speed=UTILS.KmphToMps(self.speedCruise) + end end if Depth then @@ -616,7 +634,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Dive depth is applied to all other waypoints. if self.depth then wp.alt=-self.depth - else + else -- Take default waypoint depth. end @@ -1051,8 +1069,7 @@ function NAVYGROUP:_InitGroup() self.isNaval=true self.isGround=false - - -- Helo group. + --TODO: Submarine check --self.isSubmarine=self.group:IsSubmarine() -- Ships are always AI. @@ -1081,6 +1098,14 @@ function NAVYGROUP:_InitGroup() -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" self.option.Formation=self.optionDefault.Formation + + -- Default TACAN off. + self:SetDefaultTACAN(nil, nil, nil, nil, true) + self.tacan=UTILS.DeepCopy(self.tacanDefault) + + -- Default ICLS off. + self:SetDefaultICLS(nil, nil, nil, true) + self.icls=UTILS.DeepCopy(self.iclsDefault) -- Get all units of the group. local units=self.group:GetUnits() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index dc9030d4d..8f9db29d1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1096,6 +1096,22 @@ function OPSGROUP:GetWaypointByIndex(index) return nil end +--- Get the waypoint UID from its index, i.e. its current position in the waypoints table. +-- @param #OPSGROUP self +-- @param #number index Waypoint index. +-- @return #number Unique waypoint ID. +function OPSGROUP:GetWaypointUIDFromIndex(index) + + for i,_waypoint in pairs(self.waypoints) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + if i==index then + return waypoint.uid + end + end + + return nil +end + --- Get the waypoint index (its position in the current waypoints table). -- @param #OPSGROUP self -- @param #number uid Waypoint unique ID. @@ -1367,7 +1383,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- Debug info. - self:T(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) + self:I(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) -- Waypoint was not reached yet. if wpindex > self.currentwp then @@ -3861,7 +3877,7 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) self:SetDefaultICLS(Channel,Morse,UnitName) - self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.iclsDefault.Channel, tostring(self.iclsDefault.Morse), self.iclsDefault.BeaconName)) + self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.iclsDefault.Channel, tostring(self.iclsDefault.Morse), tostring(self.iclsDefault.BeaconName))) elseif self:IsAlive() then diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index bc7f32daa..40ba3e932 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -909,6 +909,33 @@ function UTILS.VecNorm(a) return math.sqrt(UTILS.VecDot(a, a)) end +--- Calculate the distance between two 2D vectors. +-- @param DCS#Vec2 a Vector in 3D with x, y components. +-- @param DCS#Vec2 b Vector in 3D with x, y components. +-- @return #number Distance between the vectors. +function UTILS.VecDist2D(a, b) + + local c={x=b.x-a.x, y=b.y-a.y} + + local d=math.sqrt(c.x*c.x+c.y*c.y) + + return d +end + + +--- Calculate the distance between two 3D vectors. +-- @param DCS#Vec3 a Vector in 3D with x, y, z components. +-- @param DCS#Vec3 b Vector in 3D with x, y, z components. +-- @return #number Distance between the vectors. +function UTILS.VecDist3D(a, b) + + local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z} + + local d=math.sqrt(UTILS.VecDot(c, c)) + + return d +end + --- Calculate the [cross product](https://en.wikipedia.org/wiki/Cross_product) of two 3D vectors. The result is a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.