documentation update and multi zone per stage support
This commit is contained in:
Binary file not shown.
Binary file not shown.
+30
-100
@@ -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
@@ -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!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Setting up the missions
|
## Setting up the missions
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 296 KiB |
+22
-18
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user