documentation update and multi zone per stage support

This commit is contained in:
2024-09-27 00:40:38 +02:00
parent 4f98a1d2ad
commit b7c21d0349
6 changed files with 79 additions and 121 deletions
Binary file not shown.
Binary file not shown.
+29 -99
View File
@@ -1,68 +1,4 @@
--[[
#### Why?
For Spearhead there's a lot of stages and states the mission can be in. <br/>
To make sure CAP units will be at the place where the mission maker expects them to be there's a naming convention that should help. <br/>
You as a mission maker will have full controll over where they are supposed to be, the script will take care of getting them there.
The CAP manager's goal is to provide dedicated aircraft scheduling that doesn't reset every stage reset.
Naming: CAP\_\<"A" | B"\>\<Config\>_\<Free form name\>
#### Config:
```
1 at x: [<activeStage>]<capStage>
n and n at x: [<activeStage>,<activeStage>]<capStage>
n till n at x: [<activeStage>-<activeStage>]<capStage>
n till n and n at x: [<activeStage>-<activeStage>,<activeStage>]<capStage>
n till n at Active: [<activeStage>-<activeStage>]A
divider: |
examples:
CAP_A[1-4,6]7|[5,7]8_SomeName => Will fly CAP at stage 7 when stages 1 through 4 and 6 are active and will fly CAP at 8 when 5 and 7 are active
CAP_A[2-5]5|[6]6_SomeName => Will fly CAP at stage 5 when stages 2 through 5 active and will fly CAP at 6 when 6 is active
CAP_A[1-5]A|[6]7_SomeName => Will fly CAP at the ACTIVE stage if Stages 1-5 are active. Basically following the active stages. Then when 6 is active it will fly in 7
CAP_B[1-5]A|[6]7_SomeName => Will fly BACKUP CAP for the active zones 1 through 5 and back up for 7 when 6 is active.
```
### How many? And how to add backups?
To fascilitate a nice flow of the mission and also make sure it doesn't oversaturate the zones with aircraft the script works with a Active/Backup system in the naming. <br/>
This really doesn't mean much per se once the mission runs, but most importantly is that the A units define how many groups there should be max in a zone at a time. <br/>
The B units will simply be used to fill that amount if the A units can't due to RTB, Death, Rearming etc. <br/>
#### Example
Take the units:
```
CAP_A[1-5]A_SomeName1
CAP_A[1-5]A_SomeName2
CAP_B[1-3,5]A_SomeName
```
`CAP_A...` units are primary units where the `CAP_B...` units are the backups. <br/>
In this case the CAP manager sees that for stages 1 through 5 this configuration requires 2 groups in the active zone. <br/>
If one of those 2 groups dies or is going back to base the B group will be used to top up the CAP units at that zone. <br/>
After scheduling the B units the A units that are back at base ready on the ramp will also not be scheduled until the CAP units that are active in the zone (inlcuding B units) drop below the required CAP unit (of 2 in this example)
In this example there is no Backup unit for zone 4. This might quiet down the CAP a little as the Active groups will have to rearm and refuel without there being any backup.
### What the cap manager does:
- Spawn aircraft on the ramp (or despawn when they are not needed anymore for culling)
- Send out aircraft based on where they are supposed to be
- Send Aircraft RTB after X time. <br/>
RTB in this sense means back to its base of origin. Not the closest friendly base like DCS does.
- Simulates Rearming and then sending them out when needed.
- Delays aircraft for X amount of time before spawning and rearming after a groups demise.
- Aircraft are spawned on the ramp so OCA does have effect. (Be sure to also take a look at the Airbase and SAM spawning for defences)
### Future Ideas
- Aircraft rearm hubs with finite spawns on other airbases that get replenished by aircraft flying in.
]] --
local CapHelper = {} local CapHelper = {}
do do
@@ -193,7 +129,6 @@ function CapGroup:new(groupName, airbaseId, logger, database, capConfig)
o.isBackup = parsed.isBackup o.isBackup = parsed.isBackup
--vars --vars
o.assignedStageName = nil
o.assignedStageNumber = nil o.assignedStageNumber = nil
o.state = CapGroup.GroupState.UNSPAWNED o.state = CapGroup.GroupState.UNSPAWNED
@@ -294,47 +229,42 @@ function CapGroup:new(groupName, airbaseId, logger, database, capConfig)
return --Can't task a unit that's dead or RTB return --Can't task a unit that's dead or RTB
end end
local stageZoneName = self.database:getStageZoneByStageNumber(stageZoneNumber)
self.assignedStageNumber = stageZoneNumber self.assignedStageNumber = stageZoneNumber
self.assignedStageName = stageZoneName
local group = Group.getByName(self.groupName) local group = Group.getByName(self.groupName)
if group and group:isExist() then if group and group:isExist() then
local zone = Spearhead.DcsUtil.getZoneByName(stageZoneName) self.logger:debug("Sending group out " .. self.groupName)
if zone then local controller = group:getController()
self.logger:debug("Sending group out " .. self.groupName) local capPoints = database:getCapRouteInZone(stageZoneNumber, self.airbaseId)
local controller = group:getController()
local capPoints = database:getCapRouteInZone(stageZoneName, self.airbaseId) or { point1 = { x = zone.x, z = zone.z }, point2 = nil }
local altitude = math.random(self.capConfig.minAlt, self.capConfig.maxAlt) local altitude = math.random(self.capConfig.minAlt, self.capConfig.maxAlt)
local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed) local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed)
local attackHelos = false local attackHelos = false
local deviationDistance = self.capConfig.maxDeviationRange local deviationDistance = self.capConfig.maxDeviationRange
local capTask local capTask
if self.state == CapGroup.GroupState.ONRAMP or self.onStationSince == 0 then if self.state == CapGroup.GroupState.ONRAMP or self.onStationSince == 0 then
controller:setCommand({ controller:setCommand({
id = 'Start', id = 'Start',
params = {} params = {}
}) })
local duration = math.random(self.capConfig.minDurationOnStation, self.capConfig local duration = math.random(self.capConfig.minDurationOnStation, self.capConfig
.maxDurationOnstation) .maxDurationOnstation)
self.logger:debug("random schedule min: " .. self.logger:debug("random schedule min: " ..
tostring(self.capConfig.minDurationOnStation or "nil") .. tostring(self.capConfig.minDurationOnStation or "nil") ..
" max: " .. tostring(self.capConfig.maxDurationOnstation or "nil") .. " actual " .. duration) " max: " .. tostring(self.capConfig.maxDurationOnstation or "nil") .. " actual " .. duration)
self.currentCapTaskingDuration = duration self.currentCapTaskingDuration = duration
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance) capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
else else
local duration = self.currentCapTaskingDuration - (timer.getTime() - o.onStationSince) local duration = self.currentCapTaskingDuration - (timer.getTime() - o.onStationSince)
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance) capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
end
if capTask then
timer.scheduleFunction(setTaskAsync,
{ task = capTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
end
self:SetState(CapGroup.GroupState.INTRANSIT)
end end
if capTask then
timer.scheduleFunction(setTaskAsync,
{ task = capTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
end
self:SetState(CapGroup.GroupState.INTRANSIT)
end end
end end
+27 -3
View File
@@ -42,9 +42,33 @@ A CAP group needs to follow the following naming convention: `CAP_<A|B><CONFIG>_
For details on config read this: [CAP Group Config](./Reference.html#cap-group-config) For details on config read this: [CAP Group Config](./Reference.html#cap-group-config)
<img src="img/starting_stages.png" alt="drawing" width="35%"/> For now I set up 3 groups with the following names. `CAP_A[1]1_Rota1` , `CAP_A[1]1_Rota1-1` , `CAP_B[1]1_Rota1` <br/>
<img src="img/starting_stages.png" alt="drawing" width="35%"/> The first two are marked with `A` and will therefore be primary CAP units. They will be scheduled and make up for the total count. <br/>
<br/> Meaning that for this airbase there is 2 CAP units max at a time flying out. <br/>
In this case all groups have `[1]1` in the name, (This would be the same as `[1]A`) which means that when stage 1 is active the groups will activate and fly out to stage 1.
I also set up a few groups further back. One example: `CAP_A[1-3]3_Group1`. This group will protect zone 3 when zones 1 through 3 are active.
CAP units fly out, fly their CAP zone for x amount of minutes and will then RTB. <br/>
Before they actually RTB an event is triggered 10 minutes before the actual RTB task. This event will trigger a backup unit to startup and fly out to take over. <br/>
Best is to test it out and see for yourself. <br/>
### Creating CAP routes
Creating cap routes is not needed per se, but with a multi-stage stage (we have 2 stages with `_1_`) it is recommended. <br/>
Similarly with huge stages. <br/>
If there is multiple zones is will "round-robin" over them. <br/>
If no CAP route is present the unit will fly a route generated differently per zone: <br/>
`quad zone` => race-track between the corner closest to the origin airbase to the center point of the zone <br/>
`circle zone` => race-track between the closest point on circle to the origin airbase to the center <br/>
If you want to create you own CAP Routes you can! <br/>
For this example I created 2 CAP routes inside of the 2 `_1_` stages. <br/>
As you can see below there's a nice quick you can exploit. As long as the `X` of the zone is inside of the the `CAPROUTE` will be used!
![CAP Routes Image](./img/cap_routes.png)
## Setting up the missions ## Setting up the missions
Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

+22 -18
View File
@@ -30,6 +30,7 @@ do -- DB
o.tables.cap_route_zones = {} o.tables.cap_route_zones = {}
o.tables.stage_zonesByNumer = {} o.tables.stage_zonesByNumer = {}
o.tables.stage_numberPerzone = {}
do -- INIT ZONE TABLES do -- INIT ZONE TABLES
if env.mission.triggers and env.mission.triggers.zones then if env.mission.triggers and env.mission.triggers.zones then
@@ -43,11 +44,10 @@ do -- DB
if split_string[2] then if split_string[2] then
local stringified = tostring(split_string[2]) or "unknown" local stringified = tostring(split_string[2]) or "unknown"
if o.tables.stage_zonesByNumer[stringified] == nil then if o.tables.stage_zonesByNumer[stringified] == nil then
o.tables.stage_zonesByNumer[stringified] = zone_name o.tables.stage_zonesByNumer[stringified] = {}
else
table.insert(Spearhead.MissionEditingWarnings, "Duplicate Stage Order number found. This zone will work, but will not be part of the CAP script")
end end
table.insert(o.tables.stage_zonesByNumer[stringified], zone_name)
o.tables.stage_numberPerzone[zone_name] = stringified
end end
end end
@@ -384,19 +384,23 @@ do -- DB
cleanup() cleanup()
--- key: zoneName value: { current, routes = [ { point1, point2 } ] } --- key: zoneName value: { current, routes = [ { point1, point2 } ] }
o.tables.capRoutes = {} o.tables.capRoutesPerStageNumber = {}
for _, zoneName in pairs(o.tables.stage_zones) do for _, zoneName in pairs(o.tables.stage_zones) do
local number = tostring(o.tables.stage_numberPerzone[zoneName] or "unknown")
if o.tables.capRoutesPerStageNumber[number] == nil then
o.tables.capRoutesPerStageNumber[number] = {
current = 0,
routes = {}
}
end
local configValue = {
current = 0,
routes = {}
}
for _, cap_route_zone in pairs(o.tables.cap_route_zones) do for _, cap_route_zone in pairs(o.tables.cap_route_zones) do
if Spearhead.DcsUtil.isZoneInZone(cap_route_zone, zoneName) == true then if Spearhead.DcsUtil.isZoneInZone(cap_route_zone, zoneName) == true then
local zone = Spearhead.DcsUtil.getZoneByName(cap_route_zone) local zone = Spearhead.DcsUtil.getZoneByName(cap_route_zone)
if zone then if zone then
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
table.insert(configValue.routes, { point1 = { x = zone.x , z = zone.z }, point2 = nil } ) table.insert(o.tables.capRoutesPerStageNumber[number].routes, { point1 = { x = zone.x , z = zone.z }, point2 = nil } )
else else
local function getDist(a, b) local function getDist(a, b)
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2) return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
@@ -422,7 +426,7 @@ do -- DB
end end
if biggestA and biggestB then if biggestA and biggestB then
table.insert(configValue.routes, table.insert(o.tables.capRoutesPerStageNumber[number].routes,
{ {
point1 = { x = biggestA.x , z = biggestA.z }, point1 = { x = biggestA.x , z = biggestA.z },
point2 = { x = biggestB.x , z = biggestB.z } point2 = { x = biggestB.x , z = biggestB.z }
@@ -432,8 +436,8 @@ do -- DB
end end
end end
end end
o.tables.capRoutes[zoneName] = configValue
end end
o.Logger:debug(o.tables.capRoutesPerStageNumber)
o.tables.missionCodes = {} o.tables.missionCodes = {}
end end
@@ -442,9 +446,9 @@ do -- DB
return self.tables.descriptions[missionZoneName] return self.tables.descriptions[missionZoneName]
end end
o.getCapRouteInZone = function(self, targetZone, baseId) o.getCapRouteInZone = function(self, stageNumber, baseId)
local stageNumber = tostring(stageNumber) or "nothing"
local routeData = self.tables.capRoutes[targetZone] local routeData = self.tables.capRoutesPerStageNumber[stageNumber]
if routeData then if routeData then
local count = Spearhead.Util.tableLength(routeData.routes) local count = Spearhead.Util.tableLength(routeData.routes)
if count > 0 then if count > 0 then
@@ -452,7 +456,6 @@ do -- DB
if count < routeData.current then if count < routeData.current then
routeData.current = 1 routeData.current = 1
end end
return routeData.routes[routeData.current] return routeData.routes[routeData.current]
end end
end end
@@ -465,7 +468,8 @@ do -- DB
local aY = pC.z + vY / magV * radius; local aY = pC.z + vY / magV * radius;
return { x = aX, z = aY } return { x = aX, z = aY }
end end
local stagezone = Spearhead.DcsUtil.getZoneByName(targetZone) local stageZoneName = Spearhead.Util.randomFromList(self.tables.stage_zonesByNumer[stageNumber]) or "none"
local stagezone = Spearhead.DcsUtil.getZoneByName(stageZoneName)
if stagezone then if stagezone then
local base = Spearhead.DcsUtil.getAirbaseById(baseId) local base = Spearhead.DcsUtil.getAirbaseById(baseId)
if base then if base then
@@ -501,7 +505,7 @@ do -- DB
---@param self table ---@param self table
---@param number number ---@param number number
---@return string zoneName ---@return string zoneName
o.getStageZoneByStageNumber = function (self, number) o.getStageZonesByStageNumber = function (self, number)
local numberString = tostring(number) local numberString = tostring(number)
return self.tables.stage_zonesByNumer[numberString] return self.tables.stage_zonesByNumer[numberString]
end end