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.
+30 -100
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 = {}
do
@@ -193,7 +129,6 @@ function CapGroup:new(groupName, airbaseId, logger, database, capConfig)
o.isBackup = parsed.isBackup
--vars
o.assignedStageName = nil
o.assignedStageNumber = nil
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
end
local stageZoneName = self.database:getStageZoneByStageNumber(stageZoneNumber)
self.assignedStageNumber = stageZoneNumber
self.assignedStageName = stageZoneName
local group = Group.getByName(self.groupName)
if group and group:isExist() then
local zone = Spearhead.DcsUtil.getZoneByName(stageZoneName)
if zone then
self.logger:debug("Sending group out " .. self.groupName)
local controller = group:getController()
local capPoints = database:getCapRouteInZone(stageZoneName, self.airbaseId) or { point1 = { x = zone.x, z = zone.z }, point2 = nil }
self.logger:debug("Sending group out " .. self.groupName)
local controller = group:getController()
local capPoints = database:getCapRouteInZone(stageZoneNumber, self.airbaseId)
local altitude = math.random(self.capConfig.minAlt, self.capConfig.maxAlt)
local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed)
local attackHelos = false
local deviationDistance = self.capConfig.maxDeviationRange
local capTask
if self.state == CapGroup.GroupState.ONRAMP or self.onStationSince == 0 then
controller:setCommand({
id = 'Start',
params = {}
})
local duration = math.random(self.capConfig.minDurationOnStation, self.capConfig
.maxDurationOnstation)
self.logger:debug("random schedule min: " ..
tostring(self.capConfig.minDurationOnStation or "nil") ..
" max: " .. tostring(self.capConfig.maxDurationOnstation or "nil") .. " actual " .. duration)
self.currentCapTaskingDuration = duration
local altitude = math.random(self.capConfig.minAlt, self.capConfig.maxAlt)
local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed)
local attackHelos = false
local deviationDistance = self.capConfig.maxDeviationRange
local capTask
if self.state == CapGroup.GroupState.ONRAMP or self.onStationSince == 0 then
controller:setCommand({
id = 'Start',
params = {}
})
local duration = math.random(self.capConfig.minDurationOnStation, self.capConfig
.maxDurationOnstation)
self.logger:debug("random schedule min: " ..
tostring(self.capConfig.minDurationOnStation or "nil") ..
" max: " .. tostring(self.capConfig.maxDurationOnstation or "nil") .. " actual " .. duration)
self.currentCapTaskingDuration = duration
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
else
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)
end
if capTask then
timer.scheduleFunction(setTaskAsync,
{ task = capTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
end
self:SetState(CapGroup.GroupState.INTRANSIT)
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
else
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)
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
+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)
<img src="img/starting_stages.png" alt="drawing" width="35%"/>
<img src="img/starting_stages.png" alt="drawing" width="35%"/>
<br/>
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/>
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/>
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
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.stage_zonesByNumer = {}
o.tables.stage_numberPerzone = {}
do -- INIT ZONE TABLES
if env.mission.triggers and env.mission.triggers.zones then
@@ -43,11 +44,10 @@ do -- DB
if split_string[2] then
local stringified = tostring(split_string[2]) or "unknown"
if o.tables.stage_zonesByNumer[stringified] == nil then
o.tables.stage_zonesByNumer[stringified] = zone_name
else
table.insert(Spearhead.MissionEditingWarnings, "Duplicate Stage Order number found. This zone will work, but will not be part of the CAP script")
o.tables.stage_zonesByNumer[stringified] = {}
end
table.insert(o.tables.stage_zonesByNumer[stringified], zone_name)
o.tables.stage_numberPerzone[zone_name] = stringified
end
end
@@ -384,19 +384,23 @@ do -- DB
cleanup()
--- key: zoneName value: { current, routes = [ { point1, point2 } ] }
o.tables.capRoutes = {}
o.tables.capRoutesPerStageNumber = {}
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
if Spearhead.DcsUtil.isZoneInZone(cap_route_zone, zoneName) == true then
local zone = Spearhead.DcsUtil.getZoneByName(cap_route_zone)
if zone 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
local function getDist(a, b)
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
@@ -422,7 +426,7 @@ do -- DB
end
if biggestA and biggestB then
table.insert(configValue.routes,
table.insert(o.tables.capRoutesPerStageNumber[number].routes,
{
point1 = { x = biggestA.x , z = biggestA.z },
point2 = { x = biggestB.x , z = biggestB.z }
@@ -432,8 +436,8 @@ do -- DB
end
end
end
o.tables.capRoutes[zoneName] = configValue
end
o.Logger:debug(o.tables.capRoutesPerStageNumber)
o.tables.missionCodes = {}
end
@@ -442,9 +446,9 @@ do -- DB
return self.tables.descriptions[missionZoneName]
end
o.getCapRouteInZone = function(self, targetZone, baseId)
local routeData = self.tables.capRoutes[targetZone]
o.getCapRouteInZone = function(self, stageNumber, baseId)
local stageNumber = tostring(stageNumber) or "nothing"
local routeData = self.tables.capRoutesPerStageNumber[stageNumber]
if routeData then
local count = Spearhead.Util.tableLength(routeData.routes)
if count > 0 then
@@ -452,7 +456,6 @@ do -- DB
if count < routeData.current then
routeData.current = 1
end
return routeData.routes[routeData.current]
end
end
@@ -465,7 +468,8 @@ do -- DB
local aY = pC.z + vY / magV * radius;
return { x = aX, z = aY }
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
local base = Spearhead.DcsUtil.getAirbaseById(baseId)
if base then
@@ -501,7 +505,7 @@ do -- DB
---@param self table
---@param number number
---@return string zoneName
o.getStageZoneByStageNumber = function (self, number)
o.getStageZonesByStageNumber = function (self, number)
local numberString = tostring(number)
return self.tables.stage_zonesByNumer[numberString]
end