migrated repo
This commit is contained in:
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Lua.diagnostics.globals": [
|
||||||
|
"missionCommands",
|
||||||
|
"env",
|
||||||
|
"Airbase",
|
||||||
|
"world",
|
||||||
|
"trigger",
|
||||||
|
"StaticObject",
|
||||||
|
"Group",
|
||||||
|
"Unit",
|
||||||
|
"Object",
|
||||||
|
"coalition",
|
||||||
|
"timer",
|
||||||
|
"AI"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
|
||||||
|
Order = [
|
||||||
|
"spearhead_defaults.lua",
|
||||||
|
"spearhead_base.lua",
|
||||||
|
"spearhead_events.lua",
|
||||||
|
"spearhead_dcsbase.lua",
|
||||||
|
"spearhead_routes.lua",
|
||||||
|
"spearhead_mission.lua",
|
||||||
|
"spearhead_db.lua",
|
||||||
|
"spearhead_stage.lua",
|
||||||
|
"spearhead_cap.lua",
|
||||||
|
"spearhead.lua"
|
||||||
|
]
|
||||||
|
|
||||||
|
def compile(root, target):
|
||||||
|
compiled = ""
|
||||||
|
for filename in Order:
|
||||||
|
path = os.path.join(root, filename)
|
||||||
|
with open(path,'r') as file:
|
||||||
|
fileContents = file.read()
|
||||||
|
compiled += fileContents
|
||||||
|
print(path)
|
||||||
|
|
||||||
|
with open(target, "w") as targetFile:
|
||||||
|
targetFile.write(compiled)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__" :
|
||||||
|
args = sys.argv[1:]
|
||||||
|
root = args[0]
|
||||||
|
target = args[1]
|
||||||
|
|
||||||
|
print(f"Source: {root}")
|
||||||
|
print(f"target: {target}")
|
||||||
|
|
||||||
|
compile(root, target)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
---KEEP THIS LINE
|
||||||
|
if not SpearheadConfig then SpearheadConfig = {} end
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
|
||||||
|
local CapBase = {}
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param airbaseId number
|
||||||
|
---@param database table
|
||||||
|
---@param logger table
|
||||||
|
---@param capConfig table
|
||||||
|
---@param stageConfig table
|
||||||
|
---@return table
|
||||||
|
function CapBase:new(airbaseId, database, logger, capConfig, stageConfig)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, { __index = self })
|
||||||
|
|
||||||
|
o.groupNames = database:getCapGroupsAtAirbase(airbaseId)
|
||||||
|
o.database = database
|
||||||
|
o.airbaseId = airbaseId
|
||||||
|
o.logger = logger
|
||||||
|
o.activeStage = 0
|
||||||
|
o.capConfig = capConfig
|
||||||
|
o.spawned = false
|
||||||
|
|
||||||
|
if capConfig == nil then
|
||||||
|
capConfig = {}
|
||||||
|
table.insert(Spearhead.MissionEditingWarnings,"CapConfig is nil")
|
||||||
|
else
|
||||||
|
if capConfig.minSpeed == nil then Spearhead.MissionEditingWarnings("CapConfig.minSpeed is nil") end
|
||||||
|
if capConfig.maxSpeed == nil then Spearhead.MissionEditingWarnings("CapConfig.maxSpeed is nil") end
|
||||||
|
if capConfig.minAlt == nil then Spearhead.MissionEditingWarnings("CapConfig.minAlt is nil") end
|
||||||
|
if capConfig.maxAlt == nil then Spearhead.MissionEditingWarnings("CapConfig.maxAlt is nil") end
|
||||||
|
if capConfig.minDurationOnStation == nil then Spearhead.MissionEditingWarnings("CapConfig.minDurationOnStation is nil") end
|
||||||
|
if capConfig.maxDurationOnStation == nil then Spearhead.MissionEditingWarnings("CapConfig.maxDurationOnStation is nil") end
|
||||||
|
if capConfig.rearmDelay == nil then Spearhead.MissionEditingWarnings("CapConfig.rearmDelay is nil") end
|
||||||
|
if capConfig.deathDelay == nil then Spearhead.MissionEditingWarnings("CapConfig.deathDelay is nil") end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.activeCapStages = (stageConfig or {}).capActiveStages or 10
|
||||||
|
|
||||||
|
o.lastStatesByName = {}
|
||||||
|
o.groupsByName = {}
|
||||||
|
o.PrimaryGroups = {}
|
||||||
|
o.BackupGroups = {}
|
||||||
|
|
||||||
|
local CheckReschedulingAsync = function(self, time)
|
||||||
|
self:CheckAndScheduleCAP()
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnGroupStateUpdated = function (self, capGroup)
|
||||||
|
--[[
|
||||||
|
There is no update needed for INTRANSIT, ONSTATION or REARMING as the PREVIOUS state already was checked and nothing changes in the actual overal state.
|
||||||
|
]]--
|
||||||
|
if capGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT
|
||||||
|
or capGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION
|
||||||
|
or capGroup.state == Spearhead.internal.CapGroup.GroupState.REARMING
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
timer.scheduleFunction(CheckReschedulingAsync, self, timer.getTime() + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, name in pairs(o.groupNames) do
|
||||||
|
local capGroup = Spearhead.internal.CapGroup:new(name, airbaseId, logger, database, capConfig)
|
||||||
|
if capGroup then
|
||||||
|
o.groupsByName[name] = capGroup
|
||||||
|
|
||||||
|
if capGroup.isBackup ==true then
|
||||||
|
table.insert(o.BackupGroups, capGroup)
|
||||||
|
else
|
||||||
|
table.insert(o.PrimaryGroups, capGroup)
|
||||||
|
end
|
||||||
|
|
||||||
|
capGroup:AddOnStateUpdatedListener(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.SpawnIfApplicable = function(self)
|
||||||
|
self.logger:debug("Check spawns for airbase " .. self.airbaseId )
|
||||||
|
for groupName, capGroup in pairs(self.groupsByName) do
|
||||||
|
|
||||||
|
local activeStage = tostring(self.activeStage)
|
||||||
|
local targetStage = capGroup:GetTargetZone(activeStage)
|
||||||
|
|
||||||
|
if targetStage ~= nil and capGroup.state == Spearhead.internal.CapGroup.GroupState.UNSPAWNED then
|
||||||
|
capGroup:SpawnOnTheRamp()
|
||||||
|
self.spawned = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.CheckAndScheduleCAP = function (self)
|
||||||
|
|
||||||
|
self.logger:debug("Check taskings for airbase " .. self.airbaseId )
|
||||||
|
|
||||||
|
local countPerStage = {}
|
||||||
|
local requiredPerStage = {}
|
||||||
|
|
||||||
|
--Count back up groups that are active or reassign to the new zone if that's needed
|
||||||
|
for _, backupGroup in pairs(self.BackupGroups) do
|
||||||
|
if backupGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT or backupGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION then
|
||||||
|
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
||||||
|
if supposedTargetStage then
|
||||||
|
if supposedTargetStage ~= backupGroup.assignedStageNumber then
|
||||||
|
backupGroup:SendToStage(supposedTargetStage)
|
||||||
|
end
|
||||||
|
|
||||||
|
if countPerStage[supposedTargetStage] == nil then
|
||||||
|
countPerStage[supposedTargetStage] = 0
|
||||||
|
end
|
||||||
|
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||||
|
else
|
||||||
|
backupGroup:SendRTB()
|
||||||
|
end
|
||||||
|
elseif backupGroup.state == Spearhead.internal.CapGroup.GroupState.RTBINTEN and backupGroup:GetTargetZone(self.activeStage) ~= backupGroup.assignedStageNumber then
|
||||||
|
backupGroup:SendRTB()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--Schedule or reassign primary units if applicable
|
||||||
|
for _, primaryGroup in pairs(self.PrimaryGroups) do
|
||||||
|
local supposedTargetStage = primaryGroup:GetTargetZone(self.activeStage)
|
||||||
|
if supposedTargetStage then
|
||||||
|
if requiredPerStage[supposedTargetStage] == nil then
|
||||||
|
requiredPerStage[supposedTargetStage] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if countPerStage[supposedTargetStage] == nil
|
||||||
|
then
|
||||||
|
countPerStage[supposedTargetStage] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
requiredPerStage[supposedTargetStage] = requiredPerStage[supposedTargetStage] + 1
|
||||||
|
|
||||||
|
if primaryGroup.state == Spearhead.internal.CapGroup.GroupState.READYONRAMP then
|
||||||
|
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
||||||
|
primaryGroup:SendToStage(supposedTargetStage)
|
||||||
|
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||||
|
end
|
||||||
|
elseif primaryGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT or primaryGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION then
|
||||||
|
if supposedTargetStage ~= primaryGroup.assignedStageNumber then
|
||||||
|
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
||||||
|
primaryGroup:SendToStage(supposedTargetStage)
|
||||||
|
else
|
||||||
|
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||||
|
primaryGroup:SendRTB()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||||
|
elseif primaryGroup.state == Spearhead.internal.CapGroup.GroupState.RTBINTEN and primaryGroup:GetTargetZone(self.activeStage) ~= primaryGroup.assignedStageNumber then
|
||||||
|
primaryGroup:SendRTB()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if primaryGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT or primaryGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION then
|
||||||
|
primaryGroup:SendRTB()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, backupGroup in pairs(self.BackupGroups) do
|
||||||
|
if backupGroup.state == Spearhead.internal.CapGroup.GroupState.READYONRAMP then
|
||||||
|
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
||||||
|
if supposedTargetStage then
|
||||||
|
if countPerStage[supposedTargetStage] == nil then
|
||||||
|
countPerStage[supposedTargetStage] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
||||||
|
backupGroup:SendToStage(supposedTargetStage)
|
||||||
|
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnStageNumberChanged = function (self, number)
|
||||||
|
self.activeStage = number
|
||||||
|
self:SpawnIfApplicable()
|
||||||
|
timer.scheduleFunction(CheckReschedulingAsync, self, timer.getTime() + 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if any CAP is active when a certain stage is active
|
||||||
|
---@param self table
|
||||||
|
---@param stageNumber number
|
||||||
|
---@return boolean
|
||||||
|
o.IsBaseActiveWhenStageIsActive = function (self, stageNumber)
|
||||||
|
for _, group in pairs(self.PrimaryGroups) do
|
||||||
|
local target = group:GetTargetZone(stageNumber)
|
||||||
|
if target ~= nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.Events.AddStageNumberChangedListener(o)
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Spearhead.internal then Spearhead.internal = {} end
|
||||||
|
Spearhead.internal.CapAirbase = CapBase
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
---comment
|
||||||
|
---@param groupName string
|
||||||
|
---@return table?
|
||||||
|
CapHelper.ParseGroupName = function(groupName)
|
||||||
|
local split_string = Spearhead.Util.split_string(groupName, "_")
|
||||||
|
local partCount = Spearhead.Util.tableLength(split_string)
|
||||||
|
if partCount >= 3 then
|
||||||
|
local result = {}
|
||||||
|
result.zonesConfig = {}
|
||||||
|
|
||||||
|
-- CAP_[1-5]5|[6]6|[7]7_Sukhoi
|
||||||
|
-- CAP_[1-5,7]A|[6]7_Sukhoi
|
||||||
|
|
||||||
|
local configPart = split_string[2]
|
||||||
|
local first = configPart:sub(1, 1)
|
||||||
|
if first == "A" then
|
||||||
|
result.isBackup = false
|
||||||
|
configPart = string.sub(configPart, 2, #configPart)
|
||||||
|
elseif first == "B" then
|
||||||
|
configPart = string.sub(configPart, 2, #configPart)
|
||||||
|
result.isBackup = true
|
||||||
|
elseif first == "[" then
|
||||||
|
result.isBackup = false
|
||||||
|
else
|
||||||
|
table.insert(Spearhead.MissionEditingWarnings, "Could not parse the CAP config for group: " .. groupName)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local subsplit = Spearhead.Util.split_string(configPart, "|")
|
||||||
|
if subsplit then
|
||||||
|
for key, value in pairs(subsplit) do
|
||||||
|
local keySplit = Spearhead.Util.split_string(value, "]")
|
||||||
|
local targetZone = keySplit[2]
|
||||||
|
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||||
|
local commaSeperated = Spearhead.Util.split_string(allActives, ",")
|
||||||
|
for _, value in pairs(commaSeperated) do
|
||||||
|
local dashSeperated = Spearhead.Util.split_string(value, "-")
|
||||||
|
if Spearhead.Util.tableLength(dashSeperated) > 1 then
|
||||||
|
local from = tonumber(dashSeperated[1])
|
||||||
|
local till = tonumber(dashSeperated[2])
|
||||||
|
|
||||||
|
for i = from, till do
|
||||||
|
if targetZone == "A" then
|
||||||
|
result.zonesConfig[tostring(i)] = tostring(i)
|
||||||
|
else
|
||||||
|
result.zonesConfig[tostring(i)] = tostring(targetZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if targetZone == "A" then
|
||||||
|
result.zonesConfig[tostring(dashSeperated[1])] = tostring(dashSeperated[1])
|
||||||
|
else
|
||||||
|
result.zonesConfig[tostring(dashSeperated[1])] = tostring(targetZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
table.insert(Spearhead.MissionEditingWarnings,
|
||||||
|
"CAP Group with name: " .. groupName .. "should have at least 3 parts, but has " .. partCount)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param input table { groupName, task, logger }
|
||||||
|
---@param time number
|
||||||
|
---@return nil
|
||||||
|
local function setTaskAsync(input, time)
|
||||||
|
local task = input.task
|
||||||
|
local groupName = input.groupName
|
||||||
|
local group = Group.getByName(groupName)
|
||||||
|
|
||||||
|
if task then
|
||||||
|
group:getController():setTask(task)
|
||||||
|
if input.logger ~= nil then
|
||||||
|
input.logger:debug("task set succesfully to group " .. groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local CapGroup = {}
|
||||||
|
|
||||||
|
CapGroup.GroupState = {
|
||||||
|
UNSPAWNED = 0,
|
||||||
|
READYONRAMP = 1,
|
||||||
|
INTRANSIT = 2,
|
||||||
|
ONSTATION = 3,
|
||||||
|
RTBINTEN = 4,
|
||||||
|
RTB = 5,
|
||||||
|
DEAD = 6,
|
||||||
|
REARMING = 7,
|
||||||
|
DESPAWNED = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
local function SetReadyOnRampAsync(self, time)
|
||||||
|
self:SetState(CapGroup.GroupState.READYONRAMP)
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param groupName string
|
||||||
|
---@param airbaseId number
|
||||||
|
---@param logger table logger dependency injection
|
||||||
|
---@param database table database dependency injection
|
||||||
|
---@param capConfig table config dependency injection
|
||||||
|
---@return table? o
|
||||||
|
function CapGroup:new(groupName, airbaseId, logger, database, capConfig)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, { __index = self })
|
||||||
|
|
||||||
|
local RESPAWN_AFTER_TOUCHDOWN_SECONDS = 180
|
||||||
|
|
||||||
|
-- initials
|
||||||
|
o.groupName = groupName
|
||||||
|
o.airbaseId = airbaseId
|
||||||
|
o.logger = logger
|
||||||
|
o.database = database
|
||||||
|
|
||||||
|
local parsed = CapHelper.ParseGroupName(groupName)
|
||||||
|
if parsed == nil then return nil end
|
||||||
|
o.capZonesConfig = parsed.zonesConfig
|
||||||
|
o.isBackup = parsed.isBackup
|
||||||
|
|
||||||
|
--vars
|
||||||
|
o.assignedStageName = nil
|
||||||
|
o.assignedStageNumber = nil
|
||||||
|
|
||||||
|
o.state = CapGroup.GroupState.UNSPAWNED
|
||||||
|
o.aliveUnits = {}
|
||||||
|
o.landedUnits = {}
|
||||||
|
o.unitCount = 0
|
||||||
|
o.onStationSince = 0
|
||||||
|
o.currentCapTaskingDuration = 0
|
||||||
|
|
||||||
|
--config
|
||||||
|
o.capConfig = {}
|
||||||
|
|
||||||
|
if not capConfig then capConfig = {} end
|
||||||
|
o.capConfig.maxDeviationRange = capConfig.maxDeviationRange
|
||||||
|
o.capConfig.minSpeed = (capConfig.minSpeed or 400) * 0.514444
|
||||||
|
o.capConfig.maxSpeed = (capConfig.maxSpeed or 500) * 0.514444
|
||||||
|
o.capConfig.minAlt = (capConfig.minAlt or 8000) * 0.3048
|
||||||
|
o.capConfig.maxAlt = (capConfig.maxAlt or 27000) * 0.3048
|
||||||
|
o.capConfig.minDurationOnStation = capConfig.minDurationOnStation or 600
|
||||||
|
o.capConfig.maxDurationOnstation = capConfig.maxDurationOnStation or 1800
|
||||||
|
o.capConfig.rearmDelay = capConfig.rearmDelay or 300
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param currentActive number
|
||||||
|
---@return table
|
||||||
|
o.GetTargetZone = function (self, currentActive)
|
||||||
|
return self.capZonesConfig[tostring(currentActive)]
|
||||||
|
end
|
||||||
|
|
||||||
|
o.SetState = function(self, state)
|
||||||
|
self.state = state
|
||||||
|
self:PublishUnitUpdatedEvent()
|
||||||
|
end
|
||||||
|
|
||||||
|
o.StartRearm = function(self)
|
||||||
|
self:SpawnOnTheRamp()
|
||||||
|
self:SetState(CapGroup.GroupState.REARMING)
|
||||||
|
timer.scheduleFunction(SetReadyOnRampAsync, self, timer.getTime() + self.capConfig.rearmDelay - RESPAWN_AFTER_TOUCHDOWN_SECONDS)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.SpawnOnTheRamp = function(self)
|
||||||
|
self.aliveUnits = {}
|
||||||
|
self.landedUnits = {}
|
||||||
|
self.onStationSince = 0
|
||||||
|
|
||||||
|
local group = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName, nil, nil, true)
|
||||||
|
if group then
|
||||||
|
self.unitCount = group:getInitialSize()
|
||||||
|
|
||||||
|
if self.state == CapGroup.GroupState.UNSPAWNED then
|
||||||
|
self:SetState(CapGroup.GroupState.READYONRAMP)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, unit in pairs(group:getUnits()) do
|
||||||
|
local name = unit:getName()
|
||||||
|
self.aliveUnits[name] = true
|
||||||
|
self.landedUnits[name] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.Despawn = function(self)
|
||||||
|
Spearhead.DcsUtil.DestroyGroup(self.groupName)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.SendRTB = function(self)
|
||||||
|
local group = Group.getByName(self.groupName)
|
||||||
|
if group and group:isExist() then
|
||||||
|
local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed)
|
||||||
|
local rtbTask, errormessage = Spearhead.RouteUtil.CreateRTBMission(self.groupName, self.airbaseId, speed)
|
||||||
|
if rtbTask then
|
||||||
|
timer.scheduleFunction(setTaskAsync, { task = rtbTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
|
||||||
|
else
|
||||||
|
self.logger:error("No RTB task could be created for group: " .. self.groupName .. " due to " .. errormessage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Starts and send this group to perform CAP at a stage
|
||||||
|
---@param self any
|
||||||
|
---@param stageZoneNumber string
|
||||||
|
o.SendToStage = function(self, stageZoneNumber)
|
||||||
|
if self.state == CapGroup.GroupState.DEAD or self.state == CapGroup.GroupState.RTB then
|
||||||
|
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 }
|
||||||
|
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Starts and send a unit to another airbase
|
||||||
|
---@param self table
|
||||||
|
---@param airdomeId any
|
||||||
|
o.SendToAirbase = function(self, airdomeId)
|
||||||
|
self.airbaseId = airdomeId
|
||||||
|
local speed = math.random(self.capConfig.minSpeed, self.capConfig.maxSpeed)
|
||||||
|
local rtbTask = Spearhead.RouteUtil.CreateRTBMission(self.groupName, airdomeId, speed)
|
||||||
|
local group = Group.getByName(self.groupName)
|
||||||
|
local controller = group:getController()
|
||||||
|
controller:setCommand({
|
||||||
|
id = 'Start',
|
||||||
|
params = {}
|
||||||
|
})
|
||||||
|
timer.scheduleFunction(setTaskAsync, { task = rtbTask, groupName = self.groupName, logger = self.logger },
|
||||||
|
timer.getTime() + 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnGroupRTB = function(self, groupName)
|
||||||
|
if groupName == self.groupName then
|
||||||
|
self.logger:debug("Setting group " ..
|
||||||
|
groupName ..
|
||||||
|
" to state RTB after a total of " ..
|
||||||
|
timer.getTime() - self.onStationSince .. "s of the " .. self.currentCapTaskingDuration .. "s")
|
||||||
|
self:SetState(CapGroup.GroupState.RTB)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnGroupRTBInTen = function(self, groupName)
|
||||||
|
if groupName == self.groupName then
|
||||||
|
self:SetState(CapGroup.GroupState.RTBINTEN)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnGroupOnStation = function(self, groupName)
|
||||||
|
if groupName == self.groupName then
|
||||||
|
self.onStationSince = timer.getTime()
|
||||||
|
self.logger:debug("Setting group " .. groupName .. " to state Onstation")
|
||||||
|
self:SetState(CapGroup.GroupState.ONSTATION)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param proActive boolean Will check all units in group for aliveness
|
||||||
|
o.UpdateState = function(self, proActive)
|
||||||
|
local landed = false
|
||||||
|
local landedCount = 0
|
||||||
|
for name, landedBool in pairs(self.landedUnits) do
|
||||||
|
if landedBool == true then
|
||||||
|
landedCount = landedCount + 1
|
||||||
|
landed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local deadCount = 0
|
||||||
|
for name, isAlive in pairs(self.aliveUnits) do
|
||||||
|
if isAlive == false then
|
||||||
|
deadCount = deadCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function DelayedStartRearm(input, time)
|
||||||
|
local capGroup = input.self
|
||||||
|
capGroup:StartRearm()
|
||||||
|
end
|
||||||
|
|
||||||
|
if landedCount + deadCount == self.unitCount then
|
||||||
|
if landed then
|
||||||
|
timer.scheduleFunction(DelayedStartRearm, { self = self }, timer.getTime() + RESPAWN_AFTER_TOUCHDOWN_SECONDS)
|
||||||
|
else
|
||||||
|
local delay = self.capConfig.deathDelay - self.capConfig.rearmDelay + RESPAWN_AFTER_TOUCHDOWN_SECONDS
|
||||||
|
timer.scheduleFunction(DelayedStartRearm, { self = self }, timer.getTime() + delay)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.eventListeners = {}
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param listener table object with function OnGroupStateUpdated(capGroupTable)
|
||||||
|
o.AddOnStateUpdatedListener = function(self, listener)
|
||||||
|
if type(listener) ~= "table" then
|
||||||
|
self.logger:error("Listener not of type table for AddOnStateUpdatedListener")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if listener.OnGroupStateUpdated == nil then
|
||||||
|
self.logger:error("Listener does not implement OnGroupStateUpdated")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.insert(self.eventListeners, listener)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.PublishUnitUpdatedEvent = function(self)
|
||||||
|
for _, callable in pairs(self.eventListeners) do
|
||||||
|
local _, error = pcall(function()
|
||||||
|
callable:OnGroupStateUpdated(self)
|
||||||
|
end)
|
||||||
|
if error then
|
||||||
|
self.logger:error(error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnUnitLanded = function(self, initiatorUnit, airbase)
|
||||||
|
if airbase then
|
||||||
|
local airdomeId = airbase:getID()
|
||||||
|
self.airbaseId = airdomeId
|
||||||
|
end
|
||||||
|
local name = initiatorUnit:getName()
|
||||||
|
self.logger:debug("Received unit land event for unit " .. name .. " of group " .. self.groupName)
|
||||||
|
|
||||||
|
self.landedUnits[name] = true
|
||||||
|
self:UpdateState(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnUnitLost = function(self, initiatorUnit)
|
||||||
|
self.logger:debug("Received unit lost event for group " .. self.groupName)
|
||||||
|
if initiatorUnit then
|
||||||
|
self.aliveUnits[initiatorUnit:getName()] = false
|
||||||
|
end
|
||||||
|
self:UpdateState(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.Events.addOnGroupRTBListener(o.groupName, o)
|
||||||
|
Spearhead.Events.addOnGroupRTBInTenListener(o.groupName, o)
|
||||||
|
Spearhead.Events.addOnGroupOnStationListener(o.groupName, o)
|
||||||
|
local units = Group.getByName(groupName):getUnits()
|
||||||
|
for key, unit in pairs(units) do
|
||||||
|
Spearhead.Events.addOnUnitLandEventListener(unit:getName(), o)
|
||||||
|
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), o)
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Spearhead.internal then Spearhead.internal = {} end
|
||||||
|
Spearhead.internal.CapGroup = CapGroup
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
local GlobalCapManager = {}
|
||||||
|
do
|
||||||
|
local airbasesPerStage = {}
|
||||||
|
local allAirbasesByName = {}
|
||||||
|
local activeAirbasesPerActiveStage = {}
|
||||||
|
local unitsPerzonePerStage = {}
|
||||||
|
|
||||||
|
local initiated = false
|
||||||
|
|
||||||
|
function GlobalCapManager.start(database, capConfig, stageConfig)
|
||||||
|
if initiated == true then return end
|
||||||
|
|
||||||
|
local logger = Spearhead.LoggerTemplate:new("AirbaseManager", Spearhead.config.logLevel)
|
||||||
|
|
||||||
|
local zones = database:getStagezoneNames()
|
||||||
|
if zones then
|
||||||
|
for key, stageName in pairs(zones) do
|
||||||
|
if airbasesPerStage[stageName] == nil then
|
||||||
|
airbasesPerStage[stageName] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local airbaseIds = database:getAirbaseIdsInStage(stageName)
|
||||||
|
if airbaseIds then
|
||||||
|
for _, id in pairs(airbaseIds) do
|
||||||
|
local airbaseName = Spearhead.DcsUtil.getAirbaseName(id)
|
||||||
|
if airbaseName then
|
||||||
|
local airbaseSpecificLogger = Spearhead.LoggerTemplate:new("CAP_" .. airbaseName, Spearhead.config.logLevel)
|
||||||
|
local airbase = Spearhead.internal.CapAirbase:new(id, database, airbaseSpecificLogger, capConfig, stageConfig)
|
||||||
|
if airbase then
|
||||||
|
table.insert(airbasesPerStage[stageName], airbase)
|
||||||
|
allAirbasesByName[airbaseName] = airbase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logger:info("Initiated " .. Spearhead.Util.tableLength(allAirbasesByName) .. " airbases for cap")
|
||||||
|
initiated = true
|
||||||
|
|
||||||
|
local InfoFunctions = {}
|
||||||
|
|
||||||
|
---returns if there is CAP active
|
||||||
|
---@param zoneName any
|
||||||
|
---@param activeZoneNumber number
|
||||||
|
---@return boolean
|
||||||
|
InfoFunctions.IsCapActiveWhenZoneIsActive = function(zoneName, activeZoneNumber)
|
||||||
|
for _, airbase in pairs(airbasesPerStage[zoneName]) do
|
||||||
|
if airbase:IsBaseActiveWhenStageIsActive(activeZoneNumber) == true then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.capInfo = InfoFunctions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.internal.GlobalCapManager = GlobalCapManager
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
--[[
|
||||||
|
The Mission Manager creates a way to create missions in a stage like manner with missions without having to worry about monitoring and triggering of said missions.
|
||||||
|
|
||||||
|
The Mission manager assumes players are BLUE and are fighting RED. (Which countries and spawns, that's up to you)
|
||||||
|
|
||||||
|
Mission Naming TriggerZone:
|
||||||
|
Stage: MISSIONSTAGE_<ordernumber>_Name
|
||||||
|
Mission: MISSION_<oneOf(DEAD, STRIKE, BAI, SAM)>_Name
|
||||||
|
Random Mission: RANDOMMISSION_<tasking>_<NAME>_<number>
|
||||||
|
|
||||||
|
IMPORTANT NOTES
|
||||||
|
- DO NOT put mission zones inside of mission zones.
|
||||||
|
- DO NOT let stage zones overlap (or without there being anything indide of said overlap, this includes airbases)
|
||||||
|
|
||||||
|
|
||||||
|
Stage Naming: MISSIONSTAGE_<order[number]>_Name
|
||||||
|
NOTE: Multiple stages can have an order number.
|
||||||
|
This gives you the opportunity to add multiple zones in a stage.
|
||||||
|
All stages need to be completed for the next stage order to start.
|
||||||
|
|
||||||
|
Player SPAWNS
|
||||||
|
Airbases
|
||||||
|
It is assumed all player spawns are Dynamic slots.
|
||||||
|
These work relatively nicely nowadays and with a dynamic mission it can provide the best experience.
|
||||||
|
FARPS
|
||||||
|
Need to be in TriggerZone with name convention: "FARP_<name>"
|
||||||
|
will be removed at start and activated inside of the active stage only, so be wary of where you place them.
|
||||||
|
|
||||||
|
Mission Types and their logic:
|
||||||
|
Random Missions:
|
||||||
|
To maximise replayability randomisation is directly supported.
|
||||||
|
There is 2 ways. First you can randomise the units inside of the mission zone the second way is to randomise the mission zone altogether.
|
||||||
|
|
||||||
|
1. Randomising the units in the mission.
|
||||||
|
You can use the "Chance" function for groups to spawn or not spawn groups inside of a mission zone.
|
||||||
|
The framework will only take control over the units after the initial spawn and will therefore not spawn units that did not get spawned on initial creation.
|
||||||
|
NOTE: This however gives the least predictable outcome and can easily lead to empty missions.
|
||||||
|
|
||||||
|
2. Randomised mission zones
|
||||||
|
The best way to randomise it to create X amount of trigger zones with the same mission and let the framework pick 1 on initialisation.
|
||||||
|
Naming convention: RANDOMMISSION_<tasking>_<NAME>_<number> (eg. RANDOMMISSION_BAI_BYRON_1 and RANDOMMISSION_BAI_BYRON_2)
|
||||||
|
RANDOMMISSION: Recogniser
|
||||||
|
tasking: the tasking just like any other mission
|
||||||
|
NAME: Codename of the mission. (Use single word only for commands later on)
|
||||||
|
number: can be any number. Only intention it to make it unique for the editor to not freak out.
|
||||||
|
|
||||||
|
The framework will recognise that RANDOMMISSION_BAI_BYRON_1 and RANDOMMISSION_BAI_BYRON_2 compete against each other and will select a random one and add that to the stage.
|
||||||
|
After that a random mission will act just like any other mission.
|
||||||
|
|
||||||
|
TIP: If you want a mission that doesn't always spawn: You can do something like the following example:
|
||||||
|
- RANDOMMISSION_BAI_BYRON_1 => The mission you want to spawn about 1 every 4 times with the units and description
|
||||||
|
- RANDOMMISSION_BAI_BYRON_2 => Empty trigger zone
|
||||||
|
- RANDOMMISSION_BAI_BYRON_3 => Empty trigger zone
|
||||||
|
- RANDOMMISSION_BAI_BYRON_4 => Empty trigger zone
|
||||||
|
|
||||||
|
Special Types:
|
||||||
|
SAM
|
||||||
|
All SAM missions will be spawned during the stage, so there's no random pop-ups
|
||||||
|
SAM missions will be have slightly different ways of briefing and will be shown in the overview of a stage as "known air defenses".
|
||||||
|
SAMS however do not count towards the completion of the zone and will be despawned once all other missions are done.
|
||||||
|
SAM missions of the NEXT 2 stages (by order) will also be spawned.
|
||||||
|
This makes it so you can create defenses of airfields where CAP units are spawned and add long range defenses without having to make the stage huge.
|
||||||
|
Eg. If MISSIONSTAGE_1_Name is active then all stages with numbers 2 and 3 will also have active SAMS.
|
||||||
|
|
||||||
|
SAM vs DEAD
|
||||||
|
Generally best practive: Use SAM missions for long range sams that need to be active for longer.
|
||||||
|
Use DEAD for shorter range popup sams like moving SHORADS.
|
||||||
|
|
||||||
|
|
||||||
|
AIRBASES
|
||||||
|
Airbases have a special logic to them. This is to make sure that it's manageable which bases are used by friendly forces after pushing along.
|
||||||
|
Capturable bases can be selected and units on airbases are managed.
|
||||||
|
|
||||||
|
Logic is based on the starting coalition of the base.
|
||||||
|
RED
|
||||||
|
The base will be used for CAP of the enemy.
|
||||||
|
On Capture the base will turn NEUTRAL.
|
||||||
|
Units inside of the airport circle will despawn on the Stage completion.
|
||||||
|
NEUTRAL
|
||||||
|
Nothing will be done. It will not be used and units around the airport will not be specifically managed.
|
||||||
|
BLUE
|
||||||
|
Airbase will be set to "RED" on intialisation.
|
||||||
|
All blue units will be despawned and red units spawned on activation of the zone.
|
||||||
|
When the zone is captured by blue (by finishing the missions), all red units will be removed and all blue units inside it will spawn.
|
||||||
|
|
||||||
|
Airbase Units
|
||||||
|
All units inside of the circle of the airbase (shown in the me) and not in a mission zone will be regarded as a airbase unit and spawned when the airbase becomes active.
|
||||||
|
|
||||||
|
Missions on airbases.
|
||||||
|
Missions at airbases are perfectly possible. Any unit that is part of that mission will not be regarded as an "Airbase unit"
|
||||||
|
|
||||||
|
AWACS
|
||||||
|
ENEMY
|
||||||
|
An enemy awacs will be spawned in the active stage + 2 unless it's disabled with Config.DisableAwacs.
|
||||||
|
TODO: AWACS logic
|
||||||
|
FRIENDLY
|
||||||
|
Friendly AWACS will be spawned at the ACTIVE stage - 2. Which means that at the start there will be no awacs.
|
||||||
|
There is one awacs spawned per stage with a delay of 15 minutes delay for respawn per default.
|
||||||
|
If there is enough blue fighters a red fighter group will be spawned randomly to try and intercept the AWACS.
|
||||||
|
A message will pop up and players are expected to defend it.
|
||||||
|
This can be disabled wth Config.DisableAwacsInterceptTask
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPTERS
|
||||||
|
This area of the documentation is for mission makers that want to hook into the framework from their own scripts.
|
||||||
|
This script will expose flags of it's state, but there are no public methods to alter the framework (at this time).
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
TODO: Expose flags for stage and other metrics
|
||||||
|
]] --
|
||||||
|
|
||||||
|
--[[
|
||||||
|
TODOLIST:
|
||||||
|
- FARPS and Airbases V
|
||||||
|
- RANDOM missions
|
||||||
|
|
||||||
|
- CAP Manager
|
||||||
|
- Mission Activation
|
||||||
|
- OPTIONAL Drawings
|
||||||
|
|
||||||
|
]] --
|
||||||
|
|
||||||
|
|
||||||
|
local dbLogger = Spearhead.LoggerTemplate:new("database", Spearhead.config.logLevel)
|
||||||
|
local databaseManager = Spearhead.DB:new(dbLogger)
|
||||||
|
|
||||||
|
local capConfig = {
|
||||||
|
maxDeviationRange = 32186, --20NM -- sets max deviation before flight starts pulling back,
|
||||||
|
minSpeed = 400,
|
||||||
|
maxSpeed = 500,
|
||||||
|
minAlt = 18000,
|
||||||
|
maxAlt = 28000,
|
||||||
|
minDurationOnStation = 1800,
|
||||||
|
maxDurationOnStation = 2700,
|
||||||
|
rearmDelay = 600,
|
||||||
|
deathDelay = 1800
|
||||||
|
}
|
||||||
|
|
||||||
|
local stageConfig = {
|
||||||
|
preActivatedStages = 3,
|
||||||
|
capActiveStages = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
Spearhead.internal.GlobalCapManager.start(databaseManager, capConfig, stageConfig)
|
||||||
|
Spearhead.internal.GlobalStageManager.start(databaseManager)
|
||||||
|
|
||||||
|
local activateStage = function (number)
|
||||||
|
local succ, err = pcall( function ()
|
||||||
|
Spearhead.Events.PublishStageNumberChanged(number)
|
||||||
|
end)
|
||||||
|
env.error(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
missionCommands.addCommand("stage1", {}, activateStage, 1)
|
||||||
|
missionCommands.addCommand("stage2", {}, activateStage, 2)
|
||||||
|
missionCommands.addCommand("stage3", {}, activateStage, 3)
|
||||||
|
missionCommands.addCommand("stage4", {}, activateStage, 4)
|
||||||
|
|
||||||
|
Spearhead.LoadingDone()
|
||||||
|
--Check lines of code in directory per file:
|
||||||
|
-- Get-ChildItem . -Include *.lua -Recurse | foreach {""+(Get-Content $_).Count + " => " + $_.name }
|
||||||
|
-- find . -name '*.lua' | xargs wc -l
|
||||||
+1560
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
CAP Standby Timing
|
||||||
|
This works with 3 timing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
MONITORING AND PREDICTIONS
|
||||||
|
TODO: Make sure that before the mission is started the mission maker has a way to see how many groups(units) are scheduled for each zone.
|
||||||
|
|
||||||
|
|
||||||
|
CAP Naming
|
||||||
|
CAP_1_1_<name> means the required cap state is 1 and it's the first unit. This means these units will be the first units to be dispatched.
|
||||||
|
|
||||||
|
CAP_1-2_1_<name> means the unit cap state is 1 AND 2 and it will spawn a unique
|
||||||
|
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
--[[
|
||||||
|
TODO: Spawn groups that are in state 1 or 2 at position and then give them waypoints when tasked.
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local StageCapManager = {}
|
||||||
|
do
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.StageCapManager = StageCapManager
|
||||||
@@ -0,0 +1,589 @@
|
|||||||
|
-- 3
|
||||||
|
|
||||||
|
local SpearheadDB = {}
|
||||||
|
do -- DB
|
||||||
|
|
||||||
|
local singleton = nil
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param Logger table
|
||||||
|
---@return table
|
||||||
|
function SpearheadDB:new(Logger)
|
||||||
|
if singleton ~= nil then
|
||||||
|
Logger:info("Returning an already initiated instance of SpearheadDB")
|
||||||
|
return singleton
|
||||||
|
end
|
||||||
|
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, { __index = self })
|
||||||
|
|
||||||
|
o.Logger = Logger
|
||||||
|
o.tables = {}
|
||||||
|
do --INIT ALL TABLES
|
||||||
|
Logger:debug("Initiating tables")
|
||||||
|
|
||||||
|
o.tables.all_zones = {}
|
||||||
|
o.tables.stage_zones = {}
|
||||||
|
o.tables.mission_zones = {}
|
||||||
|
o.tables.random_mission_zones = {}
|
||||||
|
o.tables.farp_zones = {}
|
||||||
|
o.tables.cap_route_zones = {}
|
||||||
|
|
||||||
|
o.tables.stage_zonesByNumer = {}
|
||||||
|
|
||||||
|
do -- INIT ZONE TABLES
|
||||||
|
if env.mission.triggers and env.mission.triggers.zones then
|
||||||
|
for zone_ind, zone_data in pairs(env.mission.triggers.zones) do
|
||||||
|
local zone_name = zone_data.name
|
||||||
|
local split_string = Spearhead.Util.split_string(zone_name, "_")
|
||||||
|
table.insert(o.tables.all_zones, zone_name)
|
||||||
|
|
||||||
|
if string.lower(split_string[1]) == "missionstage" then
|
||||||
|
table.insert(o.tables.stage_zones, zone_name)
|
||||||
|
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")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.lower(split_string[1]) == "mission" then
|
||||||
|
table.insert(o.tables.mission_zones, zone_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.lower(split_string[1]) == "randommission" then
|
||||||
|
table.insert(o.tables.random_mission_zones, zone_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.lower(split_string[1]) == "farp" then
|
||||||
|
table.insert(o.tables.farp_zones, zone_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.lower(split_string[1]) == "caproute" then
|
||||||
|
table.insert(o.tables.cap_route_zones, zone_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger:debug("initiated zone tables, continuing with descriptions")
|
||||||
|
o.tables.descriptions = {}
|
||||||
|
do --load markers
|
||||||
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
|
for i, layer in pairs(env.mission.drawings.layers) do
|
||||||
|
if string.lower(layer.name) == "author" then
|
||||||
|
for key, layer_object in pairs(layer.objects) do
|
||||||
|
local inZone = Spearhead.DcsUtil.isPositionInZones(layer_object.mapX, layer_object.mapY, o.tables.mission_zones)
|
||||||
|
if Spearhead.Util.tableLength(inZone) >= 1 then
|
||||||
|
local name = inZone[1]
|
||||||
|
if name ~= nil then
|
||||||
|
o.tables.descriptions[name] = layer_object.text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.tables.missionZonesPerStage = {}
|
||||||
|
for key, missionZone in pairs(o.tables.mission_zones) do
|
||||||
|
local found = false
|
||||||
|
local i = 1
|
||||||
|
while found == false and i <= Spearhead.Util.tableLength(o.tables.stage_zones) do
|
||||||
|
local stageZone = o.tables.stage_zones[i]
|
||||||
|
if Spearhead.DcsUtil.isZoneInZone(missionZone, stageZone) == true then
|
||||||
|
if o.tables.missionZonesPerStage[stageZone] == nil then
|
||||||
|
o.tables.missionZonesPerStage[stageZone] = {}
|
||||||
|
end
|
||||||
|
table.insert(o.tables.missionZonesPerStage[stageZone], missionZone)
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.tables.randomMissionZonesPerStage = {}
|
||||||
|
for key, missionZone in pairs(o.tables.random_mission_zones) do
|
||||||
|
local found = false
|
||||||
|
local i = 1
|
||||||
|
while found == false and i <= Spearhead.Util.tableLength(o.tables.stage_zones) do
|
||||||
|
local stageZone = o.tables.stage_zones[i]
|
||||||
|
if Spearhead.DcsUtil.isZoneInZone(missionZone, stageZone) == true then
|
||||||
|
if o.tables.randomMissionZonesPerStage[stageZone] == nil then
|
||||||
|
o.tables.randomMissionZonesPerStage[stageZone] = {}
|
||||||
|
end
|
||||||
|
table.insert(o.tables.randomMissionZonesPerStage[stageZone], missionZone)
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local isAirbaseInZone = {}
|
||||||
|
o.tables.airbasesPerStage = {}
|
||||||
|
o.tables.farpIdsInFarpZones = {}
|
||||||
|
local airbases = world.getAirbases()
|
||||||
|
for _, airbase in pairs(airbases) do
|
||||||
|
local baseId = airbase:getID()
|
||||||
|
local point = airbase:getPoint()
|
||||||
|
local found = false
|
||||||
|
for _, zoneName in pairs(o.tables.stage_zones) do
|
||||||
|
if found == false then
|
||||||
|
if Spearhead.DcsUtil.isPositionInZone(point.x, point.z, zoneName) == true then
|
||||||
|
found = true
|
||||||
|
local baseIdString = tostring(baseId) or "nil"
|
||||||
|
isAirbaseInZone[baseIdString] = true
|
||||||
|
|
||||||
|
if airbase:getDesc().category == 0 then
|
||||||
|
--Airbase
|
||||||
|
if Spearhead.DcsUtil.getStartingCoalition(baseId) == 2 then
|
||||||
|
airbase:setCoalition(1)
|
||||||
|
airbase:autoCapture(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
if o.tables.airbasesPerStage[zoneName] == nil then
|
||||||
|
o.tables.airbasesPerStage[zoneName] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(o.tables.airbasesPerStage[zoneName], baseId)
|
||||||
|
else
|
||||||
|
-- farp
|
||||||
|
local i = 1
|
||||||
|
local farpFound = false
|
||||||
|
while farpFound == false and i <= Spearhead.Util.tableLength(o.tables.farp_zones) do
|
||||||
|
local farpZoneName = o.tables.farp_zones[i]
|
||||||
|
if Spearhead.DcsUtil.isPositionInZone(point.x, point.z, farpZoneName) == true then
|
||||||
|
farpFound = true
|
||||||
|
|
||||||
|
if o.tables.farpIdsInFarpZones[farpZoneName] == nil then
|
||||||
|
o.tables.farpIdsInFarpZones[farpZoneName] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
airbase:setCoalition(1)
|
||||||
|
airbase:autoCapture(false)
|
||||||
|
table.insert(o.tables.farpIdsInFarpZones[farpZoneName], baseIdString)
|
||||||
|
end
|
||||||
|
i = i +1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
o.tables.farpZonesPerStage = {}
|
||||||
|
for _, farpZoneName in pairs(o.tables.farp_zones) do
|
||||||
|
local findFirst = function (farpZoneName)
|
||||||
|
for _, stage_zone in pairs(o.tables.stage_zones) do
|
||||||
|
if Spearhead.DcsUtil.isZoneInZone(farpZoneName, stage_zone) then
|
||||||
|
return stage_zone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local found = findFirst(farpZoneName)
|
||||||
|
if found then
|
||||||
|
|
||||||
|
if o.tables.farpZonesPerStage[found] == nil then
|
||||||
|
o.tables.farpZonesPerStage[found] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(o.tables.farpZonesPerStage[found], farpZoneName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local is_group_taken = {}
|
||||||
|
do
|
||||||
|
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
|
||||||
|
for _, value in pairs(all_groups) do
|
||||||
|
is_group_taken[value] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local getAvailableGroups = function()
|
||||||
|
local result = {}
|
||||||
|
for name, value in pairs(is_group_taken) do
|
||||||
|
if value == false then
|
||||||
|
table.insert(result, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local getAvailableCAPGroups = function()
|
||||||
|
local result = {}
|
||||||
|
for name, value in pairs(is_group_taken) do
|
||||||
|
if value == false and Spearhead.Util.startswith(name, "CAP") then
|
||||||
|
table.insert(result, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
--- airbaseId <> groupname[]
|
||||||
|
o.tables.capGroupsOnAirbase = {}
|
||||||
|
local loadCapUnits = function()
|
||||||
|
local all_groups = getAvailableCAPGroups()
|
||||||
|
Logger:debug(all_groups)
|
||||||
|
local airbases = world.getAirbases()
|
||||||
|
for _, airbase in pairs(airbases) do
|
||||||
|
local baseId = airbase:getID()
|
||||||
|
local point = airbase:getPoint()
|
||||||
|
|
||||||
|
o.tables.capGroupsOnAirbase[baseId] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.areGroupsInCustomZone(all_groups,
|
||||||
|
{ x = point.x, z = point.z, radius = 6600 })
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.capGroupsOnAirbase[baseId], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- missionZoneName <> groupname[]
|
||||||
|
o.tables.groupsInMissionZone = {}
|
||||||
|
local loadMissionzoneUnits = function()
|
||||||
|
local all_groups = getAvailableGroups()
|
||||||
|
for _, missionZoneName in pairs(o.tables.mission_zones) do
|
||||||
|
o.tables.groupsInMissionZone[missionZoneName] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.groupsInMissionZone[missionZoneName], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- missionZoneName <> groupname[]
|
||||||
|
o.tables.groupsInRandomMissions = {}
|
||||||
|
local loadRandomMissionzoneUnits = function()
|
||||||
|
local all_groups = getAvailableGroups()
|
||||||
|
for _, missionZoneName in pairs(o.tables.random_mission_zones) do
|
||||||
|
o.tables.groupsInRandomMissions[missionZoneName] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.groupsInRandomMissions[missionZoneName], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- farpZoneName <> groupname[]
|
||||||
|
o.tables.groupsInFarpZone = {}
|
||||||
|
local loadFarpGroups = function()
|
||||||
|
local all_groups = getAvailableGroups()
|
||||||
|
for _, farpZone in pairs(o.tables.farp_zones) do
|
||||||
|
o.tables.groupsInFarpZone[farpZone] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, farpZone)
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.groupsInFarpZone[farpZone], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- farpZoneName <> groupname[]
|
||||||
|
o.tables.redAirbaseGroupsPerAirbase = {}
|
||||||
|
o.tables.blueAirbaseGroupsPerAirbase = {}
|
||||||
|
local loadAirbaseGroups = function()
|
||||||
|
local all_groups = getAvailableGroups()
|
||||||
|
local airbases = world.getAirbases()
|
||||||
|
for _, airbase in pairs(airbases) do
|
||||||
|
local baseId = tostring(airbase:getID())
|
||||||
|
local point = airbase:getPoint()
|
||||||
|
|
||||||
|
if isAirbaseInZone[tostring(baseId) or "something" ] == true then
|
||||||
|
o.tables.redAirbaseGroupsPerAirbase[baseId] = {}
|
||||||
|
o.tables.blueAirbaseGroupsPerAirbase[baseId] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.areGroupsInCustomZone(all_groups, { x = point.x, z = point.z, radius = 6600 })
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
|
||||||
|
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
|
||||||
|
local object = StaticObject.getByName(groupName)
|
||||||
|
if object then
|
||||||
|
if object:getCoalition() == coalition.side.RED then
|
||||||
|
table.insert(o.tables.redAirbaseGroupsPerAirbase[baseId], groupName)
|
||||||
|
elseif object:getCoalition() == coalition.side.BLUE then
|
||||||
|
table.insert(o.tables.blueAirbaseGroupsPerAirbase[baseId], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local group = Group.getByName(groupName)
|
||||||
|
if group then
|
||||||
|
if group:getCoalition() == coalition.side.RED then
|
||||||
|
table.insert(o.tables.redAirbaseGroupsPerAirbase[baseId], groupName)
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
elseif group:getCoalition() == coalition.side.BLUE then
|
||||||
|
table.insert(o.tables.blueAirbaseGroupsPerAirbase[baseId], groupName)
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.tables.miscGroupsInStage = {}
|
||||||
|
local loadMiscGroupsInStage = function ()
|
||||||
|
local all_groups = getAvailableGroups()
|
||||||
|
for _, stage_zone in pairs(o.tables.stage_zones) do
|
||||||
|
o.tables.miscGroupsInStage[stage_zone] = {}
|
||||||
|
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, stage_zone)
|
||||||
|
for _, groupName in pairs(groups) do
|
||||||
|
|
||||||
|
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
|
||||||
|
local object = StaticObject.getByName(groupName)
|
||||||
|
if object and object:getCoalition() ~= coalition.side.NEUTRAL then
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.miscGroupsInStage[stage_zone], groupName)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local group = Group.getByName(groupName)
|
||||||
|
if group and group:getCoalition() ~= coalition.side.NEUTRAL then
|
||||||
|
is_group_taken[groupName] = true
|
||||||
|
table.insert(o.tables.miscGroupsInStage[stage_zone], groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
loadCapUnits()
|
||||||
|
loadMissionzoneUnits()
|
||||||
|
loadRandomMissionzoneUnits()
|
||||||
|
loadFarpGroups()
|
||||||
|
loadAirbaseGroups()
|
||||||
|
loadMiscGroupsInStage()
|
||||||
|
|
||||||
|
local cleanup = function () --CLean up all groups that are now managed inside zones by spearhead
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for name, taken in pairs(is_group_taken) do
|
||||||
|
if taken == true then
|
||||||
|
Spearhead.DcsUtil.DestroyGroup(name)
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Logger:info("Destroyed " .. count .. " units that are now managed in zones by Spearhead")
|
||||||
|
end
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
--- key: zoneName value: { current, routes = [ { point1, point2 } ] }
|
||||||
|
o.tables.capRoutes = {}
|
||||||
|
for _, zoneName in pairs(o.tables.stage_zones) do
|
||||||
|
|
||||||
|
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 } )
|
||||||
|
else
|
||||||
|
local function getDist(a, b)
|
||||||
|
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local biggest = nil
|
||||||
|
local biggestA = nil
|
||||||
|
local biggestB = nil
|
||||||
|
|
||||||
|
for i=1, 3 do
|
||||||
|
for ii = i + 1, 4 do
|
||||||
|
|
||||||
|
local a = zone.verts[i]
|
||||||
|
local b = zone.verts[ii]
|
||||||
|
local dist = getDist(a,b)
|
||||||
|
|
||||||
|
if biggest == nil or dist > biggest then
|
||||||
|
biggestA = a
|
||||||
|
biggestB = b
|
||||||
|
biggest = dist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if biggestA and biggestB then
|
||||||
|
table.insert(configValue.routes,
|
||||||
|
{
|
||||||
|
point1 = { x = biggestA.x , z = biggestA.z },
|
||||||
|
point2 = { x = biggestB.x , z = biggestB.z }
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
o.tables.capRoutes[zoneName] = configValue
|
||||||
|
end
|
||||||
|
|
||||||
|
o.tables.missionCodes = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.GetDescriptionForMission = function(self, missionZoneName)
|
||||||
|
return self.tables.descriptions[missionZoneName]
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getCapRouteInZone = function(self, targetZone, baseId)
|
||||||
|
|
||||||
|
local routeData = self.tables.capRoutes[targetZone]
|
||||||
|
if routeData then
|
||||||
|
local count = Spearhead.Util.tableLength(routeData.routes)
|
||||||
|
if count > 0 then
|
||||||
|
routeData.current = routeData.current + 1
|
||||||
|
if count < routeData.current then
|
||||||
|
routeData.current = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return routeData.routes[routeData.current]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
do
|
||||||
|
local function GetClosestPointOnCircle(pC, radius, p)
|
||||||
|
local vX = p.x - pC.x;
|
||||||
|
local vY = p.z - pC.z;
|
||||||
|
local magV = math.sqrt(vX*vX + vY*vY);
|
||||||
|
local aX = pC.x + vX / magV * radius;
|
||||||
|
local aY = pC.z + vY / magV * radius;
|
||||||
|
return { x = aX, z = aY }
|
||||||
|
end
|
||||||
|
local stagezone = Spearhead.DcsUtil.getZoneByName(targetZone)
|
||||||
|
if stagezone then
|
||||||
|
local base = Spearhead.DcsUtil.getAirbaseById(baseId)
|
||||||
|
if base then
|
||||||
|
local closest = nil
|
||||||
|
if stagezone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
|
||||||
|
closest = GetClosestPointOnCircle({x = stagezone.x, z = stagezone.z}, stagezone.radius, base:getPoint())
|
||||||
|
else
|
||||||
|
local function getDist(a, b)
|
||||||
|
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local closestDistance = -1
|
||||||
|
for _, vert in pairs(stagezone.verts) do
|
||||||
|
local distance = getDist(vert, base:getPoint())
|
||||||
|
if closestDistance == -1 or distance < closestDistance then
|
||||||
|
closestDistance = distance
|
||||||
|
closest = vert
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if math.random(1,2)%2 == 0 then
|
||||||
|
return { point1 = closest, point2 = {x = stagezone.x, z = stagezone.z} }
|
||||||
|
else
|
||||||
|
return { point1 = {x = stagezone.x, z = stagezone.z}, point2 = closest }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param number number
|
||||||
|
---@return string zoneName
|
||||||
|
o.getStageZoneByStageNumber = function (self, number)
|
||||||
|
local numberString = tostring(number)
|
||||||
|
return self.tables.stage_zonesByNumer[numberString]
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@return table result a list of stage zone names
|
||||||
|
o.getStagezoneNames = function (self)
|
||||||
|
return self.tables.stage_zones
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getMissionsForStage = function (self, stagename)
|
||||||
|
return self.tables.missionZonesPerStage[stagename] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getRandomMissionsForStage = function(self, stagename)
|
||||||
|
return self.tables.randomMissionZonesPerStage[stagename] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getGroupsForMissionZone = function (self, missionZoneName)
|
||||||
|
if Spearhead.Util.startswith(missionZoneName, "RANDOMMISSION") == true then
|
||||||
|
return self.tables.groupsInRandomMissions[missionZoneName] or {}
|
||||||
|
end
|
||||||
|
return self.tables.groupsInMissionZone[missionZoneName] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getMissionBriefingForMissionZone = function (self, missionZoneName)
|
||||||
|
return self.tables.descriptions[missionZoneName] or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param stageName string
|
||||||
|
---@return table result airbase IDs. Use Spearhead.DcsUtil.getAirbaseById
|
||||||
|
o.getAirbaseIdsInStage = function (self, stageName)
|
||||||
|
return self.tables.airbasesPerStage[stageName] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getFarpZonesInStage = function (self, stageName)
|
||||||
|
return self.tables.farpZonesPerStage[stageName]
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param airbaseId number
|
||||||
|
---@return table
|
||||||
|
o.getCapGroupsAtAirbase = function(self, airbaseId)
|
||||||
|
return self.tables.capGroupsOnAirbase[airbaseId] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getRedGroupsAtAirbase = function (self, airbaseId)
|
||||||
|
local baseId = tostring(airbaseId)
|
||||||
|
return self.tables.redAirbaseGroupsPerAirbase[baseId]
|
||||||
|
end
|
||||||
|
|
||||||
|
o.getBlueGroupsAtAirbase = function (self, airbaseId)
|
||||||
|
local baseId = tostring(airbaseId)
|
||||||
|
return self.tables.blueAirbaseGroupsPerAirbase[baseId]
|
||||||
|
end
|
||||||
|
|
||||||
|
o.GetNewMissionCode = function (self)
|
||||||
|
local code = nil
|
||||||
|
local tries = 0
|
||||||
|
while code == nil and tries < 10 do
|
||||||
|
local random = math.random(1000, 9999)
|
||||||
|
if self.tables.missionCodes[random] == nil then
|
||||||
|
code = random
|
||||||
|
end
|
||||||
|
tries = tries + 1
|
||||||
|
end
|
||||||
|
return code
|
||||||
|
|
||||||
|
--[[
|
||||||
|
TODO: What to do when there's no random possible
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
|
||||||
|
do -- LOG STATE
|
||||||
|
Logger:info("initiated the database with amount of zones: ")
|
||||||
|
Logger:info("Stages: " .. Spearhead.Util.tableLength(o.tables.stage_zones))
|
||||||
|
Logger:info("Missions: " .. Spearhead.Util.tableLength(o.tables.mission_zones))
|
||||||
|
Logger:info("Random Missions: " .. Spearhead.Util.tableLength(o.tables.random_mission_zones))
|
||||||
|
Logger:info("Farps: " .. Spearhead.Util.tableLength(o.tables.farp_zones))
|
||||||
|
Logger:info("Airbases: " .. Spearhead.Util.tableLength(o.tables.airbasesPerStage))
|
||||||
|
Logger:info("RedAirbase Groups: " .. Spearhead.Util.tableLength(o.tables.redAirbaseGroupsPerAirbase["21"]))
|
||||||
|
Spearhead.Util.tableLength(o.tables.airbasesPerStage)
|
||||||
|
end
|
||||||
|
singleton = o
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.DB = SpearheadDB
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
|
||||||
|
local StagesByName = {}
|
||||||
|
|
||||||
|
|
||||||
|
GlobalStageManager = {}
|
||||||
|
GlobalStageManager.start = function (database)
|
||||||
|
|
||||||
|
for _, stageName in pairs(database:getStagezoneNames()) do
|
||||||
|
|
||||||
|
local logger = Spearhead.LoggerTemplate:new(stageName, Spearhead.config.logLevel)
|
||||||
|
local stage = Spearhead.internal.Stage:new(stageName, database, logger)
|
||||||
|
|
||||||
|
StagesByName[stageName] = stage
|
||||||
|
|
||||||
|
local logger = Spearhead.LoggerTemplate:new("StageManager", Spearhead.config.logLevel)
|
||||||
|
logger:info("Initiated " .. Spearhead.Util.tableLength(StagesByName) .. " airbases for cap")
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.internal.GlobalStageManager = GlobalStageManager
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
--[[
|
||||||
|
# 3. Missions
|
||||||
|
|
||||||
|
A mission is a completable objective with a state and a continuous check to see if itself is completed. <br/>
|
||||||
|
The delay between checks is quite big, but it also is checked on unit deaths and other events.
|
||||||
|
|
||||||
|
### Placement
|
||||||
|
The placement of MISSION trigger zones can be anywhere. <br/>
|
||||||
|
The order of unit detection is `CAP` > `MISSION` > `AIRBASE` > `STAGE` <br/>
|
||||||
|
This means that if a unit has a name that starts with `"CAP_"` it will not be included in a mission. <br/>
|
||||||
|
But all other units in a `MISSION` trigger zone will be managed as part of that mission.
|
||||||
|
|
||||||
|
Units inside a `MISSION` do not have to stay within the triggerzone. <br/>
|
||||||
|
They just need to be inside the zone at the start of the mission. <br/>
|
||||||
|
You can for example let a `BAI` mission drive back and forth between airbases. The `MISSIONZONE` only needs to be around the units that are part of the objective, not the waypoints.
|
||||||
|
|
||||||
|
### Naming
|
||||||
|
`MISSION_<type>_<name>` <br/>
|
||||||
|
`RANDOMMISSION_<type>_<name>_<index>` (Read RANDOMISATION below)
|
||||||
|
|
||||||
|
With: <br/>
|
||||||
|
`name` = A name that is easy to remember and type. Like a codename. Exmaples: BYRON, PLUKE, etc. <br/>
|
||||||
|
`type` = any of the below described types. Special types are marked with an *
|
||||||
|
|
||||||
|
TIP: You can click on the type to get more details
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>SAM*</summary>
|
||||||
|
  SAM Sites are managed a little different. SAM Sites can be used to guide players and to protect airfields. <br/>
|
||||||
|
  In the future when deepstrike missions might come into scope these SAM sites will also be more important. <br/>
|
||||||
|
  SAM sites will be activated when a zone is "Pre-Active". <br/>
|
||||||
|
  A stage is "Pre-Active" when there is a CAP base active, or there is other things to do that would need the SAM site to be live (OCA, DEEPSTRIKE, EXTRACTION \<= all feature development)<br/>
|
||||||
|
  If you want a SAM site to become active ONLY when the stage is fully active, then `DEAD` is the type for you!
|
||||||
|
|
||||||
|
  <u>Completion logic</u> <br/>
|
||||||
|
  TODO: documentation: Completion logic
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>DEAD</summary>
|
||||||
|
|
||||||
|
  DEAD missions will be spawned on activation of the stage. <br/>
|
||||||
|
  ALL DEAD missions will be activated right at the start of a stage. <br/>
|
||||||
|
  This might be against the "randomisation" feel, but it is to make sure mission don't get activated randomly and players get ambushed by a random spawn. <br/>
|
||||||
|
|
||||||
|
  <u>Completion logic</u> <br/>
|
||||||
|
  TODO: documentation: Completion logic
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>BAI</summary>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>STRIKE</summary>
|
||||||
|
  STRIKE missions will be activated randomly until all of them are completed. <br/>
|
||||||
|
  A strike mission can be placed anywhere, even on airbases
|
||||||
|
|
||||||
|
  <u>Completion logic</u> <br/>
|
||||||
|
  TODO: documentation: Completion logic
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Randomisation
|
||||||
|
|
||||||
|
You can randomise missions. <br/>
|
||||||
|
Spearhead will pick up all mission zones that start with `"RANDOMMISSION_"` <br/>
|
||||||
|
Then it will combine each `RANDOMMISSION` in a zone with the same `<name>` and pick a random one. <br/>
|
||||||
|
It will always pick 1 and only 1.
|
||||||
|
|
||||||
|
This means you have some options for randomisation. <br/>
|
||||||
|
For example, if you have 1 missionzone with name `RANDOMMISSION_SAM_PLUKE_1` that is filled with an SA-2 and another zone with `RANDOMMISSION_SAM_PLUKE_2` filled with an SA-3 then some runs of the mission it will spawn an SA-3 and sometimes spawn an SA-3. (works for any mission type)
|
||||||
|
|
||||||
|
What you can also do is add empty `RANDOMMISSION_` zones next to the filled `RANDOMMISSION_` zone.
|
||||||
|
For example. You have a `RANDOMMISSION_DEAD_BYRON_1` filled with an SA-19 driving around and 2 more `RANDOMMISSION_DEAD_BYRON_<2 & 3>` zones then it will have a 33% chance of being spawned.
|
||||||
|
If a zone is empty it will not be briefed, activated or count towards completion of the `STAGE`
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- A mission Object.
|
||||||
|
local Mission = {}
|
||||||
|
do -- INIT Mission Class
|
||||||
|
|
||||||
|
local MINIMAL_UNITS_ALIVE_RATIO = 0.20
|
||||||
|
|
||||||
|
local Defaults = {}
|
||||||
|
Defaults.MainMenu = "Missions"
|
||||||
|
Defaults.SelectMenuSubMenus = { Defaults.MainMenu, "Select Mission" }
|
||||||
|
Defaults.ShowMissionSubs = { Defaults.MainMenu }
|
||||||
|
|
||||||
|
local PlayersInMission = {}
|
||||||
|
local MissionType = {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
STRIKE = 1,
|
||||||
|
BAI = 2,
|
||||||
|
DEAD = 3,
|
||||||
|
SAM = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
do --INIT MISSION TYPE FUNCTIONS
|
||||||
|
---Parse string to mission type
|
||||||
|
---@param input string
|
||||||
|
MissionType.Parse = function(input)
|
||||||
|
if input == nil then
|
||||||
|
return Mission.MissionType.UNKNOWN
|
||||||
|
end
|
||||||
|
|
||||||
|
input = string.lower(input)
|
||||||
|
if input == "dead" then return MissionType.DEAD end
|
||||||
|
if input == "strike" then return MissionType.STRIKE end
|
||||||
|
if input == "bai" then return MissionType.BAI end
|
||||||
|
if input == "sam" then return MissionType.SAM end
|
||||||
|
return Mission.MissionType.UNKNOWN
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param input number missionType
|
||||||
|
---@return string text
|
||||||
|
MissionType.toString = function(input)
|
||||||
|
if input == MissionType.DEAD then return "DEAD" end
|
||||||
|
if input == MissionType.STRIKE then return "STRIKE" end
|
||||||
|
if input == MissionType.BAI then return "BAI" end
|
||||||
|
if input == MissionType.SAM then return "SAM" end
|
||||||
|
return "?"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Mission.MissionType = MissionType
|
||||||
|
|
||||||
|
Mission.MissionState = {
|
||||||
|
NEW = 0,
|
||||||
|
ACTIVE = 1,
|
||||||
|
COMPLETED = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param missionZoneName string missionZoneName
|
||||||
|
---@param database table db dependency injection
|
||||||
|
---@return table?
|
||||||
|
function Mission:new(missionZoneName, database, logger)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, { __index = self })
|
||||||
|
|
||||||
|
local function ParseGroupName(input)
|
||||||
|
local split_name = Spearhead.Util.split_string(input, "_")
|
||||||
|
local split_length = Spearhead.Util.tableLength(split_name)
|
||||||
|
if Spearhead.Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
|
||||||
|
Spearhead.AddMissionEditorWarning("Random Mission with zonename " .. input .. " not in right format")
|
||||||
|
return nil
|
||||||
|
elseif split_length < 3 then
|
||||||
|
Spearhead.AddMissionEditorWarning("Mission with zonename" .. input .. " not in right format")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local type = split_name[2]
|
||||||
|
local parsedType = Mission.MissionType.Parse(type)
|
||||||
|
|
||||||
|
if parsedType == nil then
|
||||||
|
Spearhead.AddMissionEditorWarning("Mission with zonename '" .. input .. "' has an unsupported type '" .. (type or "nil" ))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local name = split_name[3]
|
||||||
|
return {
|
||||||
|
missionName = name,
|
||||||
|
type = parsedType
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local parsed = ParseGroupName(missionZoneName)
|
||||||
|
if parsed == nil then return nil end
|
||||||
|
|
||||||
|
o.missionZoneName = missionZoneName
|
||||||
|
o.database = database
|
||||||
|
o.groupNames = database:getGroupsForMissionZone(missionZoneName)
|
||||||
|
o.name = parsed.missionName
|
||||||
|
o.missionType = parsed.type
|
||||||
|
o.startingGroups = Spearhead.Util.tableLength(o.groupNames)
|
||||||
|
o.missionState = Mission.MissionState.NEW
|
||||||
|
o.missionbriefing = database:GetDescriptionForMission(missionZoneName)
|
||||||
|
o.startingUnits = 0
|
||||||
|
o.logger = logger
|
||||||
|
o.code = database:GetNewMissionCode()
|
||||||
|
|
||||||
|
o.groupUnitAliveDict = {}
|
||||||
|
o.targetAliveStates = {}
|
||||||
|
o.hasSpecificTargets = false
|
||||||
|
|
||||||
|
local CheckStateAsync = function (self, time)
|
||||||
|
self:CheckAndUpdateSelf()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnUnitLost = function(self, object)
|
||||||
|
--[[
|
||||||
|
OnUnit lost event
|
||||||
|
]]--
|
||||||
|
local category = object:getCategory()
|
||||||
|
if category == Object.Category.UNIT then
|
||||||
|
local unitName = object:getName()
|
||||||
|
local groupName = object:getGroup():getName()
|
||||||
|
self.groupUnitAliveDict[groupName][unitName] = false
|
||||||
|
|
||||||
|
if self.targetAliveStates[groupName][unitName] then
|
||||||
|
self.targetAliveStates[groupName][unitName] = false
|
||||||
|
end
|
||||||
|
elseif category == Object.Category.STATIC then
|
||||||
|
local name = object:getName()
|
||||||
|
self.groupUnitAliveDict[name][name] = false
|
||||||
|
|
||||||
|
if self.targetAliveStates[name][name] then
|
||||||
|
self.targetAliveStates[name][name] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
CheckStateAsync(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.MissionCompleteListeners = {}
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param listener table Object that implements "OnMissionComplete(self, mission)"
|
||||||
|
o.AddMissionCompleteListener = function(self, listener)
|
||||||
|
if type(listener) ~= "table" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(self.MissionCompleteListeners, listener)
|
||||||
|
end
|
||||||
|
|
||||||
|
local TriggerMissionComplete = function(self)
|
||||||
|
for _, callable in pairs(self.MissionCompleteListeners) do
|
||||||
|
local succ, err = pcall( function()
|
||||||
|
callable:OnMissionComplete(self)
|
||||||
|
end)
|
||||||
|
if err then
|
||||||
|
self.logger:warn("Error in misstion complete listener:" .. err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local StartCheckingAndUpdateSelfContinuous = function (self)
|
||||||
|
local CheckAndUpdate = function(self, time)
|
||||||
|
self:CheckAndUpdateSelf(true)
|
||||||
|
if self.missionState == Mission.MissionState.COMPLETED or self.missionState == Mission.MissionState.NEW then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return time + 60
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
timer.scheduleFunction(CheckAndUpdate, self, timer.getTime() + 300)
|
||||||
|
end
|
||||||
|
|
||||||
|
local CleanupDelayedAsync = function (self, time)
|
||||||
|
self:Cleanup()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param self table
|
||||||
|
---@param checkUnitHealth boolean?
|
||||||
|
o.CheckAndUpdateSelf = function(self, checkUnitHealth)
|
||||||
|
if not checkUnitHealth then checkUnitHealth = false end
|
||||||
|
|
||||||
|
if self.missionState == Mission.MissionState.COMPLETED then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
--[[
|
||||||
|
TODO: Check own state based on mission type
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local specificTargetsAlive = false
|
||||||
|
if self.hasSpecificTargets == true then
|
||||||
|
for groupName, unitNameDict in pairs(self.targetAliveStates) do
|
||||||
|
for unitName, isAlive in pairs(unitNameDict) do
|
||||||
|
if isAlive == true then
|
||||||
|
specificTargetsAlive = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
|
||||||
|
local function CountAliveGroups()
|
||||||
|
local aliveGroups = 0
|
||||||
|
|
||||||
|
for _, group in pairs(self.groupUnitAliveDict) do
|
||||||
|
local groupTotal = 0
|
||||||
|
local groupDeath = 0
|
||||||
|
for _, isAlive in pairs(group) do
|
||||||
|
if isAlive ~= true then
|
||||||
|
groupDeath = groupDeath + 1
|
||||||
|
end
|
||||||
|
groupTotal = groupTotal + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local aliveRatio = (groupTotal - groupDeath) / groupTotal
|
||||||
|
if aliveRatio >= MINIMAL_UNITS_ALIVE_RATIO then
|
||||||
|
aliveGroups = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.missionType == Mission.MissionType.STRIKE then --strike targets should normally have TGT targets
|
||||||
|
if CountAliveGroups() == 0 then
|
||||||
|
self.missionState = Mission.MissionState.COMPLETED
|
||||||
|
end
|
||||||
|
elseif self.missionType == Mission.MissionType.BAI then
|
||||||
|
if CountAliveGroups() == 0 then
|
||||||
|
self.missionState = Mission.MissionState.COMPLETED
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--[[
|
||||||
|
TODO: Other checks for mission complete
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if self.missionState == Mission.MissionState.COMPLETED then
|
||||||
|
TriggerMissionComplete(self)
|
||||||
|
--Schedule cleanup after 5 minutes of mission complete
|
||||||
|
timer.scheduleFunction(CleanupDelayedAsync, self, timer.getTime() + 300)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Activates groups for this mission
|
||||||
|
---@param self table
|
||||||
|
o.Activate = function(self)
|
||||||
|
if self.missionState == Mission.MissionState.ACTIVE then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.missionState = Mission.MissionState.ACTIVE
|
||||||
|
do --spawn groups
|
||||||
|
for key, groupname in pairs(self.groupNames) do
|
||||||
|
Spearhead.DcsUtil.SpawnGroupTemplate(groupname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
StartCheckingAndUpdateSelfContinuous(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.ShowBriefing = function(self, unitId)
|
||||||
|
local text = "Mission #" .. self.code .. "\n" .. self.missionbriefing .. " \n \nState TODO"
|
||||||
|
trigger.action.outTextForUnit(unitId, text, 30);
|
||||||
|
end
|
||||||
|
|
||||||
|
o.Cleanup = function(self)
|
||||||
|
for key, groupName in pairs(self.groupNames) do
|
||||||
|
Spearhead.DcsUtil.DestroyGroup(groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local Init = function(self)
|
||||||
|
for key, group_name in pairs(self.groupNames) do
|
||||||
|
|
||||||
|
self.groupUnitAliveDict[group_name] = {}
|
||||||
|
self.targetAliveStates[group_name] = {}
|
||||||
|
|
||||||
|
if Spearhead.DcsUtil.IsGroupStatic(group_name) then
|
||||||
|
self.startingUnits = self.startingUnits + 1
|
||||||
|
Spearhead.Events.addOnUnitLostEventListener(group_name, self)
|
||||||
|
|
||||||
|
self.groupUnitAliveDict[group_name][group_name] = true
|
||||||
|
if Spearhead.Util.startswith(group_name, "TGT_") == true then
|
||||||
|
self.targetAliveStates[group_name][group_name] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
local group = Group.getByName(group_name)
|
||||||
|
local isGroupTarget = Spearhead.Util.startswith(group_name, "TGT_")
|
||||||
|
|
||||||
|
self.startingUnits = self.startingUnits + group:getInitialSize()
|
||||||
|
for _, unit in pairs(group:getUnits()) do
|
||||||
|
local unitName = unit:getName()
|
||||||
|
Spearhead.Events.addOnUnitLostEventListener(unitName, self)
|
||||||
|
self.groupUnitAliveDict[group_name][unitName] = true
|
||||||
|
|
||||||
|
if isGroupTarget == true or Spearhead.Util.startswith(unitName, "TGT_") == true then
|
||||||
|
self.targetAliveStates[group_name][unitName] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Spearhead.Util.tableLength(self.targetAliveStates) > 0 then
|
||||||
|
self.hasSpecificTargets = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Init(o)
|
||||||
|
return o;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.internal.Mission = Mission
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
# 2. Stages
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
local Stage = {}
|
||||||
|
do --init STAGE DIRECTOR
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param stagezone_name string
|
||||||
|
---@param database table
|
||||||
|
---@param logger table
|
||||||
|
---@return table?
|
||||||
|
function Stage:new(stagezone_name, database, logger)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, { __index = self })
|
||||||
|
|
||||||
|
o.zoneName = stagezone_name
|
||||||
|
|
||||||
|
local split = Spearhead.Util.split_string(stagezone_name, "_")
|
||||||
|
if Spearhead.Util.tableLength(split) < 2 then
|
||||||
|
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stagezone_name .. " does not have a order number or valid format")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local orderNumber = tonumber(split[2])
|
||||||
|
if orderNumber == nil then
|
||||||
|
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stagezone_name .. " does not have a valid order number : " .. split[2])
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
o.stageNumber = orderNumber
|
||||||
|
o.database = database
|
||||||
|
|
||||||
|
o.db = {}
|
||||||
|
o.db.missions = {}
|
||||||
|
o.db.sams = {}
|
||||||
|
o.db.redAirbasegroups = {}
|
||||||
|
o.db.blueAirbasegroups = {}
|
||||||
|
o.db.airbaseIds = {}
|
||||||
|
o.db.farps = {}
|
||||||
|
o.preActivated = false
|
||||||
|
|
||||||
|
do --Init Stage
|
||||||
|
logger:info("Initiating new Stage with name: " .. stagezone_name)
|
||||||
|
|
||||||
|
local missionZones = database:getMissionsForStage(stagezone_name)
|
||||||
|
for _, missionZone in pairs(missionZones) do
|
||||||
|
local mission = Spearhead.internal.Mission:new(missionZone, database)
|
||||||
|
if mission then
|
||||||
|
if mission.missionType == Spearhead.internal.Mission.MissionType.SAM then
|
||||||
|
table.insert(o.db.sams, mission)
|
||||||
|
else
|
||||||
|
table.insert(o.db.missions, mission)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local randomMissionNames = database:getRandomMissionsForStage(stagezone_name)
|
||||||
|
|
||||||
|
local randomMissionByName = {}
|
||||||
|
for _, missionZoneName in pairs(randomMissionNames) do
|
||||||
|
local mission = Spearhead.internal.Mission:new(missionZoneName, database)
|
||||||
|
if mission then
|
||||||
|
if randomMissionByName[mission.name] == nil then
|
||||||
|
randomMissionByName[mission.name] = {}
|
||||||
|
end
|
||||||
|
table.insert(randomMissionByName[mission.name], mission)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, missions in pairs(randomMissionByName) do
|
||||||
|
local mission = Spearhead.Util.randomFromList(missions)
|
||||||
|
if mission then
|
||||||
|
if mission.missionType == Spearhead.internal.Mission.MissionType.SAM then
|
||||||
|
table.insert(o.db.sams, mission)
|
||||||
|
else
|
||||||
|
table.insert(o.db.missions, mission)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local airbaseIds = database:getAirbaseIdsInStage(o.zoneName)
|
||||||
|
if airbaseIds ~= nil and type(airbaseIds) == "table" then
|
||||||
|
o.db.airbaseIds = airbaseIds
|
||||||
|
for _, airbaseId in pairs(airbaseIds) do
|
||||||
|
|
||||||
|
for _, groupName in pairs(database:getRedGroupsAtAirbase(airbaseId)) do
|
||||||
|
logger:debug("blaat)")
|
||||||
|
table.insert(o.db.redAirbasegroups, groupName)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, groupName in pairs(database:getBlueGroupsAtAirbase(airbaseId)) do
|
||||||
|
table.insert(o.db.blueAirbasegroups, groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local farps = database:getFarpZonesInStage(o.zoneName)
|
||||||
|
if farps ~= nil and type(farps) == "table" then o.db.farps = farps end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.IsComplete = function(self)
|
||||||
|
for i, mission in pairs(self.db.missions) do
|
||||||
|
local state = mission:GetState()
|
||||||
|
if state == Spearhead.Mission.MissionState.ACTIVE or state == Spearhead.Mission.MissionState.NEW then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---Activates all SAMS, Airbase units etc all at once.
|
||||||
|
---@param self table
|
||||||
|
o.PreActivate = function(self)
|
||||||
|
if self.preActivated == true then return end
|
||||||
|
self.preActivated = true
|
||||||
|
for key, mission in pairs(self.db.sams) do
|
||||||
|
if mission and mission.Activate then
|
||||||
|
mission:Activate()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logger:debug("blaat Pre-activating stage with airbase groups amount: " .. Spearhead.Util.tableLength(self.db.redAirbasegroups))
|
||||||
|
|
||||||
|
for _ , groupName in pairs(self.db.redAirbasegroups) do
|
||||||
|
Spearhead.DcsUtil.SpawnGroupTemplate(groupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.ActiveStage = function(self)
|
||||||
|
if self.preActivated == false then
|
||||||
|
self:PreActivate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
TODO: Activate Stage
|
||||||
|
]]--
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
o.ActivateAllMissions = function(self)
|
||||||
|
for _, mission in pairs(self.db.missions) do
|
||||||
|
mission:Activate()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cleans up all missions
|
||||||
|
---@param self table
|
||||||
|
o.Clean = function(self)
|
||||||
|
for key, mission in pairs(self.db.missions) do
|
||||||
|
mission:Cleanup()
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, samMission in pairs(self.db.sams) do
|
||||||
|
samMission:Cleanup()
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, airbase in pairs(self.db.airbases) do
|
||||||
|
for _, redGroupName in pairs(airbase.redAirbaseGroupNames) do
|
||||||
|
Spearhead.DcsUtil.DcsUtil.DestroyGroup(redGroupName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logger:debug("'" .. Spearhead.Util.toString(self.zoneName) .. "' cleaned")
|
||||||
|
end
|
||||||
|
|
||||||
|
local ActivateBlueAsync = function(self)
|
||||||
|
for key, airbaseId in pairs(self.db.airbaseIds) do
|
||||||
|
local airbase = Spearhead.DcsUtil.getAirbaseById(airbaseId)
|
||||||
|
|
||||||
|
if airbase then
|
||||||
|
local startingCoalition = Spearhead.DcsUtil.getStartingCoalition(airbaseId)
|
||||||
|
if startingCoalition == coalition.side.BLUE then
|
||||||
|
airbase:setCoalition(2)
|
||||||
|
for _, blueGroupName in pairs(self.db.blueAirbasegroups) do
|
||||||
|
Spearhead.DcsUtil.SpawnGroupTemplate(blueGroupName)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
airbase:setCoalition(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Sets airfields to blue and spawns friendly farps
|
||||||
|
o.ActivateBlueStage = function(self)
|
||||||
|
logger:debug("Setting stage '" .. Spearhead.Util.toString(self.zoneName) .. "' to blue")
|
||||||
|
|
||||||
|
for _, groupName in pairs(self.db.redAirbasegroups) do
|
||||||
|
Spearhead.DcsUtil.DestroyGroup(groupName)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, mission in pairs(self.db.missions) do
|
||||||
|
mission:Cleanup()
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, mission in pairs(self.db.sams) do
|
||||||
|
mission:Cleanup()
|
||||||
|
end
|
||||||
|
|
||||||
|
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
|
||||||
|
|
||||||
|
-- for key, farp in pairs(self.db.farps) do
|
||||||
|
-- if farp.helipadnames then
|
||||||
|
-- for _, helipadName in pairs(farp.helipadnames) do
|
||||||
|
-- local helipad = Airbase.getByName(helipadName)
|
||||||
|
-- if helipad then
|
||||||
|
-- logger:debug("Enabling: '" .. helipad:getName() .. "'")
|
||||||
|
-- helipad:setCoalition(2)
|
||||||
|
-- else
|
||||||
|
-- logger:warn(helipadName .. " not found when spawning farps")
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if farp.group_names then
|
||||||
|
-- for _, groupName in pairs(farp.group_names) do
|
||||||
|
-- Spearhead.DcsUtil.SpawnGroupTemplate(groupName)
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
|
||||||
|
o.ManageStage = function(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
o.OnStageNumberChanged = function (self, number)
|
||||||
|
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||||
|
self:PreActivate()
|
||||||
|
end
|
||||||
|
|
||||||
|
if number == self.stageNumber then
|
||||||
|
self:ActiveStage()
|
||||||
|
end
|
||||||
|
|
||||||
|
if number > self.stageNumber then
|
||||||
|
self:ActivateBlueStage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.Events.AddStageNumberChangedListener(o)
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Spearhead.internal.Stage = Stage
|
||||||
Reference in New Issue
Block a user