2 Commits
Author SHA1 Message Date
dutchie031 52f840c270 Initial Deep Strike implementation 2026-09-08 15:47:11 +02:00
dutchie031 d14a0dc78e Added mission type deepstrike 2026-09-08 08:13:17 +02:00
65 changed files with 1011 additions and 1182 deletions
-140
View File
@@ -1,140 +0,0 @@
name: PR Prerequisites
on:
pull_request:
branches:
- main
- develop
env:
LUA_VERSION: "5.1"
LUA_LANGUAGE_SERVER_VERSION: "3.19.1"
jobs:
bundles_without_error:
runs-on: ubuntu-latest
name: Bundles Without Errors
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Lua
uses: leafo/gh-actions-lua@v13
with:
luaVersion: ${{ env.LUA_VERSION }}
- name: Compile Spearhead
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
with:
source-root: ./src
output-file: ./output/spearhead.lua
- name: Verify Compiled Lua Syntax
run: lua -e "assert(loadfile('./output/spearhead.lua'))"
- name: Set up Luarocks
uses: leafo/gh-actions-luarocks@v4
- name: Install luacheck
run: luarocks install luacheck
# "lfs",
# "setmetatable", "getmetatable", "table", "string", "math", "pairs", "ipairs",
# "require", "type", "tostring", "io", "error", "pcall", "tonumber",
# "loadstring",
- name: Create LuaCheck Config
run: |
cat << EOF > .luacheckrc
stds.spearhead_lib = {
globals = {
"SpearheadConfig", "SpearheadAPI", "GlobalCapCallBacks",
}
}
stds.dcs_lib = {
globals = {
"env", "net", "trigger", "timer", "Object", "land", "coord", "coalition",
"Unit", "Group", "Airbase", "StaticObject", "SceneryObject", "world",
"country", "missionCommands", "Controller", "Weapon", "atmosphere", "country",
"lfs", "AI"
}
}
std = "lua51c+spearhead_lib+dcs_lib"
self = false
max_line_length = false
EOF
- name: Show LuaCheck Config
run: cat .luacheckrc
- name: Lint Compiled Output
run: luacheck ./output/spearhead.lua
lua_check:
runs-on: ubuntu-latest
name: Lua Check
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Lua
uses: leafo/gh-actions-lua@v13
with:
luaVersion: ${{ env.LUA_VERSION }}
- name: Install Dcs Plugins
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/install-lua-addon@install-lua-addon/v1
with:
destination-path: "./.vscode/lua-addons" ##same as local so .luarc.json can be the same
- name: Verify Dcs Plugins Installation
run: ls -la ./.vscode/lua-addons
- name: Install lua-language-server
run: |
mkdir -p ./luals
# For Ubuntu/Debian
wget https://github.com/LuaLS/lua-language-server/releases/download/${{ env.LUA_LANGUAGE_SERVER_VERSION }}/lua-language-server-${{ env.LUA_LANGUAGE_SERVER_VERSION }}-linux-x64.tar.gz
tar -xzf lua-language-server-*.tar.gz -C ./luals
rm lua-language-server-*.tar.gz
- name: Verify Lua Language Server Installation
run: ./luals/bin/lua-language-server --version
- name: Update Json
run: |
FULL_PATH=$(realpath ./.vscode/lua-addons)
echo "full-path: $FULL_PATH"
ls -R $FULL_PATH
echo "Updating .luarc.json with full path"
jq --arg full_path "$FULL_PATH" '.workspace.library[0] = $full_path' ./.luarc.json > ./.luarc.tmp.json
mv ./.luarc.tmp.json ./.luarc.json
- name: Show Config
run: cat ./.luarc.json
- name: Run Lua Language Server
run: |
CONFIG_FULL_PATH=$(realpath ./.luarc.json)
echo "Config full path: $CONFIG_FULL_PATH"
./luals/bin/lua-language-server \
--check ./src \
--checklevel=Warning \
--loglevel=trace \
--logpath="./luals/logs" \
--configpath="$CONFIG_FULL_PATH" \
# --develop
- name: Show Log
if: failure()
run: |
ls -R ./luals/logs
cat ./luals/logs/* || true
+2 -2
View File
@@ -1,4 +1,4 @@
name: Publish Beta Release name: Publish Release
on: on:
push: push:
@@ -46,7 +46,7 @@ jobs:
echo "EOF" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT"
- name: Compile Spearhead - name: Compile Spearhead
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1 uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-action@action/v0
with: with:
source-root: ./src source-root: ./src
output-file: ./output/spearhead.lua output-file: ./output/spearhead.lua
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
echo "EOF" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT"
- name: Compile Spearhead - name: Compile Spearhead
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1 uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-action@action/v0
with: with:
source-root: ./src source-root: ./src
output-file: ./output/spearhead.lua output-file: ./output/spearhead.lua
-1
View File
@@ -3,6 +3,5 @@
/dist /dist
.vscode/lua-addons
.vscode/settings.json .vscode/settings.json
**\settings.json **\settings.json
-51
View File
@@ -1,51 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "Lua 5.1"
},
"workspace": {
"checkThirdParty": false,
"maxPreload": 1600,
"preloadFileSize": 1000,
"ignoreDir": [ "config.lua" ]
},
"diagnostics": {
"enable": true,
"globals": [
"lfs"
],
"groupSeverity": {
"duplicate": "Warning",
"global":"Warning",
"luadoc": "Warning",
"redefined": "Warning",
"strong": "Warning",
"type-check": "Warning",
"unbalanced" : "Warning",
"ambiguity": "Warning",
"strict": "Warning",
"unused": "Warning"
},
"groupFileStatus": {
"ambiguity": "Any",
"await": "Any",
"duplicate": "Any",
"global": "Any",
"luadoc": "Any",
"redefined": "Any",
"strict": "Any",
"type-check": "Any",
"unbalanced": "Any",
"unused": "Any",
"strong": "Any"
},
"ignoredFiles": "Opened",
"libraryFiles": "Opened"
},
"hint": {
"enable": false
},
"completion": {
"enable": true
}
}
+4 -5
View File
@@ -1,6 +1,5 @@
{ {
"recommendations": [ "recommendations": [
"dutchie031.dutchies-dcs-scripting-tools", "sumneko.lua"
"sumneko.lua", ]
]
} }
+39 -29
View File
@@ -1,43 +1,53 @@
# Spearhead # Spearhead
Hello and welcome to Spearhead!
If you're a developer that wants to contribute, look at the code or understand what we're doing, feel free to read on. ## Contributing.
If you're a mission maker that wants to see the documentation and build you mission with this framework. <br> I'm always happy to see contributions! <br/>
It's best to go to our fully fledged documentation website: Especially if you've found an issue, annoyance or feature that you know how to solve, fix or create! <br/>
- Stable/Main: https://spearhead.rocks
- Beta/Develop: https://beta.spearhead.rocks
## Project Structure ### Keep it small.
The Spearhead project is structured in a lot of separate files. <br> Keeping your contributions small and to the point helps both me and yourself. <br/>
While Lua isn't specifically created for it, most of it has been written with the standards of OOP in mind. <br> It'll help the PR review and the speed of improvements. <br/>
Classes, Objects and helper methods. If you have a big feature in mind? Go for it! <br/>
Does it require an entire refactor of parts or all of the program? Maybe make sure to do step 0 first.
You're find in the `/src` folder all the files. <br> ### Steps (for first timers)
They are separated in a somewhat logical order, but some things might feel out of place. <br> 0. Reach out (Optional)
### Bundling, Intellisense, Linting If you want to let me know you want to do something to contribute, please do! <br/>
Maybe I'm already working on the thing you wanted to build, or someone else is. <br/>
Creating an issue is even better! <br/>
In DCS the easiest way to import a script is with 1 single file, however, that's not as "maintainable".
In order to keep it maintainable we're using a self-built tool to bundle, validate and rewrite some of the lua in order to get it into a single file.
The tool is open-source here: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools. <br>
It has a `VSCode` extensions and two `Github Action` steps. <br>
If your favorite IDE isn't `VSCode` (I respect that), it might be very easy to create an extension for it, if it does allow that.
The `VSCode` extensions comes with Intellisense as well, in the form of a full file of DCS lua type annotations. <br> 1. Fork the repository
This gives the Lua Language Server by Sumneko (which can be used in `VSCode`) all the type definitions it needs.
When writing Spearhead code it is also important to keep our own LuaLS Annotations up to date and complete. <br>
Read all about those annotations here: https://luals.github.io/wiki/annotations/
When a pull-request to the `develop` or `main` branches is created, before it can be merged it requires to pass the linter checks. By forking you can create your own working set of code. <br/>
The first linting check is done by the Lua Language Server. It checks for whitespace and formatting, but it also checks if all types, type-checking and casts are in order. It marks unused variables etc. `.luarc.json` has all the config for those checks. <br> Whether you keep that fork public or private is up to you! <br/>
The second linter job is with `luacheck` which first "bundles" the script, and then runs a full linter check over the bundled script. <br>
This is to make sure that also the bundled script does not contain any errors.
## Contributing Keep in mind, forking is alright, but it's best with the intent to contribute back. <br/>
After all, instead of having 5 slightly different versions and products, it might be nicer to have 1 much better version. <br/>
### Setting up the project 2. Create a Draft PR as soon as possible!
If you've kept on reading I assume you either want to brwose As soon as you've found some times to create an initial version, please create a draft PR. <br/>
That way you can let me and everyone know it's being worked on and people can see what conflicts might arise with their own changes. <br/>
3. Test, test, test
With DCS and Lua there's a bunch of edge cases that are really hard to get to. <br/>
However, you kinda need to make sure most if not all cases are caught and tested. <br/>
Since automated testing for now doesn't seem feasible, please make sure to verify and test functionality after touching stuff. <br/>
4. Finalise and publish the PR.
Finalise the PR and let the maintainers know! <br/>
We can all have a look and discuss the changes. <br/>
# TODO:
- Would it be possible to generate optional markers on mission briefing locations for Tomcat / Phantom navigation? Then Jester can type it in for the pilots automatically.
+85 -87
View File
@@ -1,87 +1,85 @@
## [Unreleased] ## [Unreleased]
### Breaking Changes ### Breaking Changes
### New Features ### New Features
- Addresses #41 - Issue: #41
Adds Stage Complete debug methods for local testing of the Stage flow. Adds Stage Complete debug methods for local testing of the Stage flow.
PR #40 #40
- Closes #35
Creating Lane option for stages. - Issue: #35
Additionally to the primary stage lane, stages can now be created in different lanes. Creating Lane option for stages.
These lanes can be used to create different stage progression flows and create different "side-stories". Additionally to the primary stage lane, stages can now be created in different lanes.
PR #40 These lanes can be used to create different stage progression flows and create different "side-stories".
- Closes #37 #40
Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
PR #44 - Issue: #37
- Closes #42 Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
DEEPSTRIKE now added as a possible feature. #44
This enables to have missions active in "Pre-Activated" Stages.
PR #48 ### Bug Fixes
### Bug Fixes ## [0.13.0] 2026-08
## [0.13.0] 2026-08 A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea.
A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea. ### Breaking Changes
### Breaking Changes ### New Features
### New Features - Issue #26
Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player.
- Issue #26 PR #31
Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player.
PR #31 ### Bug Fixes
### Bug Fixes - Issue #11
Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
- Issue #11 Additionally different units will spawn in different areas depending on loading side.
Supply crate spawning now checks for free space and will not spawn if the area is too crowded. PR #17
Additionally different units will spawn in different areas depending on loading side. - Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone.
PR #17 PR #15
- Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. - Fixed custom drawings not being drawn correctly.
PR #15 PR #18
- Fixed custom drawings not being drawn correctly. - Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue.
PR #18 PR #18
- Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue. - Fixed #9
PR #18 Changed order of checking mission briefings
- Fixed #9 PR #19
Changed order of checking mission briefings - Fixed Configuration defaulting to true for all booleans in StageConfig
PR #19 PR #28
- Fixed Configuration defaulting to true for all booleans in StageConfig - Fixed Pre-Activated stages not always drawing or pre-activating correctly.
PR #28 PR #28
- Fixed Pre-Activated stages not always drawing or pre-activating correctly. - Addressed #24
PR #28 CAP max commit range is now configurable in the config.lua file.
- Addressed #24 Issue remains open in order to apply further fine grained tuning.
CAP max commit range is now configurable in the config.lua file. PR #29
Issue remains open in order to apply further fine grained tuning.
PR #29 ## [0.12.0] 2026-06
## [0.12.0] 2026-06 Migration to Gitea. <br>
First version of the Gitea repository. <br>
Migration to Gitea. <br> Everything is new, but also old.
First version of the Gitea repository. <br>
Everything is new, but also old. ### Breaking Changes
### Breaking Changes Nothing worth mentioning
Nothing worth mentioning ### New Features
### New Features All and none
All and none ### Bug Fixes
### Bug Fixes No bugs fixed, they're all still there.
No bugs fixed, they're all still there. ## [x.x.x] Year - Month Release Template
## [x.x.x] Year - Month Release Template ### Breaking Changes
### Breaking Changes
### New Features
### New Features ### Bug Fixes
### Bug Fixes
+3 -3
View File
@@ -15,7 +15,7 @@ SpearheadConfig = {
--quickly enable of disable the entire CAP Logic --quickly enable of disable the entire CAP Logic
--(you can also just rename all units to not be named "CAP_") --(you can also just rename all units to not be named "CAP_")
enabled = true, -- default true enabled = true, -- default true
--min ground speed for CAP aircraft during patrol --min ground speed for CAP aircraft during patrol
-- unit: knots -- unit: knots
minSpeed = 400, -- default 400 minSpeed = 400, -- default 400
@@ -62,7 +62,7 @@ SpearheadConfig = {
deathDelay = 1800, -- default 1800 deathDelay = 1800, -- default 1800
}, },
StageConfig = { StageConfig = {
-- management of stages and its missions. -- management of stages and its missions.
-- This is not related to CAP managers which will continue to work even if stage management is disabled -- This is not related to CAP managers which will continue to work even if stage management is disabled
enabled = true, -- default true enabled = true, -- default true
@@ -98,7 +98,7 @@ SpearheadConfig = {
}, },
Persistence = { Persistence = {
--- io and lfs cannot be sanitized in the MissionScripting.lua --- io and lfs cannot be sanitized in the MissionScripting.lua
--- enables or disables the persistence logic in spearhead --- enables or disables the persistence logic in spearhead
enabled = false, enabled = false,
+73
View File
@@ -0,0 +1,73 @@
--assert(loadfile("C:\\Repos\\DCS\\Spearhead\\dev\\dev_classes.lua"))()
local basePath = "C:\\Repos\\DCS\\Spearhead\\"
local classPath = basePath .. "classes\\"
assert(loadfile(classPath .. "_baseClasses\\Queue.lua"))()
assert(loadfile(classPath .. "spearhead_base.lua"))()
assert(loadfile(classPath .. "spearhead_routeutil.lua"))()
assert(loadfile(classPath .. "spearhead_events.lua"))()
assert(loadfile(classPath .. "spearhead_db.lua"))()
assert(loadfile(classPath .. "fleetClasses\\FleetGroup.lua"))()
assert(loadfile(classPath .. "fleetClasses\\GlobalFleetManager.lua"))()
assert(loadfile(classPath .. "helpers\\MizGroupsManager.lua"))()
assert(loadfile(classPath .. "helpers\\SpawnManager.lua"))()
assert(loadfile(classPath .. "configuration\\CapConfig.lua"))()
assert(loadfile(classPath .. "configuration\\StageConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\GlobalStageManager.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\baseMissions\\Mission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\ZoneMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\RunwayStrikeMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\BuildableMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\BaseStage\\Stage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\PrimaryStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\ExtraStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\WaitingStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadGroup.lua"))()
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadSceneryObject.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\MissionCommandsHelper.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyUnitsTracker.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\BattleManager.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\abstract\\BuildableZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\StageBase.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\RTB.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\CAP.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\SWEEP.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\INTERCEPT.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\AirGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\CapGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\SweepGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\InterceptGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\detection\\DetectionManager.lua"))()
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()
assert(loadfile(classPath .. "capClasses\\CapAirbase.lua"))()
assert(loadfile(classPath .. "capClasses\\runwayBombing\\RunwayBombingTracker.lua"))()
assert(loadfile(classPath .. "persistence\\Persistence.lua"))()
-- Startup:
assert(loadfile(basePath .. "main.lua"))()
+79
View File
@@ -0,0 +1,79 @@
SpearheadConfig = {
debugEnabled = true,
CapConfig = {
--quickly enable of disable the entire CAP Logic
--(you can also just rename all units to not be named "CAP_")
enabled = true, -- default true
--min ground speed for CAP aircraft during patrol
-- unit: knots
minSpeed = 400, -- default 400
--max speed for CAP aircraft during patrol
-- unit: knots
maxSpeed = 500, -- default 500
--minAlt for aircraft on patrol
-- unit: feet
minAlt = 18000, -- default 18000
--maxAlt for aircraft on patrol
-- unit: feet
maxAlt = 28000, -- default 28000
--Delay for aircraft from touchdown to off the chocks.
-- unit: seconds
rearmDelay = 180, -- default 600
repairDelay = 600, -- default 600
--Delay for aircraft from death to takeoff.
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
-- !! Can not be lower than rearmDelay
-- unit: seconds
deathDelay = 1800, -- default 1800
},
StageConfig = {
-- management of stages and its missions.
-- This is not related to CAP managers which will continue to work even if stage management is disabled
enabled = true, -- default true
--Will draw the active and the next stage
drawStages = true, -- default true
drawPreActivated = true,
markLastContact = true,
--AutoStages will continue to the next stage automatically on completion of the missions within the stage.
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
--[[
TODO: Add manual stage transition documentation
]]
autoStages = true, --default true
--Maximum missions per stage (includes all types of missions)
maxMissionStage = 100,
--Stage starting number
startingStage = 1,
---DEBUG logging. Consider keeping this disabled
debugEnabled = true
},
Persistence = {
--- io and lfs cannot be sanitized in the MissionScripting.lua
--- enables or disables the persistence logic in spearhead
enabled = true,
--- sets the directory where the persistence file is stored
--- if nil then lfs.writedir() will be used.
--- which will
directory = nil ,
--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.
fileName = "Spearhead_Persistence_Dev"
}
}
+47
View File
@@ -0,0 +1,47 @@
---@class Queue
---@field private _items Array
---@field private _first number
---@field private _last number
local Queue = {}
Queue.__index = Queue
---@return Queue
function Queue.new()
local self = setmetatable({}, Queue)
self._items = {}
self._first = 1
self._last = 0
return self
end
---@return nil
function Queue:push(item)
self._last = self._last + 1
self._items[self._last] = item
end
---@return any?
function Queue:pop()
if self._first > self._last then
return nil
end
local item = self._items[self._first]
self._items[self._first] = nil
self._first = self._first + 1
return item
end
---@return Array<any>
function Queue:toList()
local items = {}
for i = self._first, self._last do
items[#items + 1] = self._items[i]
end
return items
end
return Queue
+20 -16
View File
@@ -2,6 +2,7 @@ local Util = require("classes.util.Util")
local CapGroup = require("classes.capClasses.airGroups.CapGroup") local CapGroup = require("classes.capClasses.airGroups.CapGroup")
local SweepGroup = require("classes.capClasses.airGroups.SweepGroup") local SweepGroup = require("classes.capClasses.airGroups.SweepGroup")
local InterceptGroup = require("classes.capClasses.airGroups.InterceptGroup") local InterceptGroup = require("classes.capClasses.airGroups.InterceptGroup")
local RunwayBombingTracker = require("classes.capClasses.runwayBombing.RunwayBombingTracker")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local RunwayStrikeMission = require("classes.stageClasses.missions.RunwayStrikeMission") local RunwayStrikeMission = require("classes.stageClasses.missions.RunwayStrikeMission")
@@ -36,11 +37,12 @@ end
---@param database Database ---@param database Database
---@param logger table ---@param logger table
---@param capConfig table ---@param capConfig table
---@param stageConfig table
---@param runwayBombingTracker RunwayBombingTracker ---@param runwayBombingTracker RunwayBombingTracker
---@param detectionManager DetectionManager ---@param detectionManager DetectionManager
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@return CapBase ---@return CapBase
function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTracker, detectionManager, spawnManager) function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
CapBase.__index = CapBase CapBase.__index = CapBase
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]] local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
@@ -59,7 +61,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTrac
local baseData = database:getAirbaseDataForZone(airbaseName) local baseData = database:getAirbaseDataForZone(airbaseName)
if baseData and baseData.CapGroups then if baseData and baseData.CapGroups then
for _, name in pairs(baseData.CapGroups) do for key, name in pairs(baseData.CapGroups) do
local capGroup = CapGroup.New(name, capConfig, logger, spawnManager) local capGroup = CapGroup.New(name, capConfig, logger, spawnManager)
if capGroup then if capGroup then
self.capGroupsByName[name] = capGroup self.capGroupsByName[name] = capGroup
@@ -68,7 +70,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTrac
end end
if baseData and baseData.SweepGroups then if baseData and baseData.SweepGroups then
for _, name in pairs(baseData.SweepGroups) do for key, name in pairs(baseData.SweepGroups) do
local sweepGroup = SweepGroup.New(name, capConfig, logger, spawnManager) local sweepGroup = SweepGroup.New(name, capConfig, logger, spawnManager)
if sweepGroup then if sweepGroup then
self.sweepGroupsByName[name] = sweepGroup self.sweepGroupsByName[name] = sweepGroup
@@ -77,7 +79,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTrac
end end
if baseData and baseData.InterceptGroups then if baseData and baseData.InterceptGroups then
for _, name in pairs(baseData.InterceptGroups) do for key, name in pairs(baseData.InterceptGroups) do
local interceptGroup = InterceptGroup.New(name, capConfig, logger, detectionManager, spawnManager) local interceptGroup = InterceptGroup.New(name, capConfig, logger, detectionManager, spawnManager)
if interceptGroup then if interceptGroup then
self.interceptGroupsByName[name] = interceptGroup self.interceptGroupsByName[name] = interceptGroup
@@ -95,6 +97,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTrac
SpearheadEvents.AddStageNumberChangedListener(self) SpearheadEvents.AddStageNumberChangedListener(self)
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15) timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
return self return self
end end
@@ -124,7 +127,7 @@ end
function CapBase:SpawnIfApplicable() function CapBase:SpawnIfApplicable()
self.logger:debug("Check spawns for airbase " .. self.airbaseName) self.logger:debug("Check spawns for airbase " .. self.airbaseName)
for _, capGroup in pairs(self.capGroupsByName) do for groupName, capGroup in pairs(self.capGroupsByName) do
local targetStage = capGroup:GetZoneIDWhenStageID(tostring(self.activeStage)) local targetStage = capGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then
@@ -132,7 +135,7 @@ function CapBase:SpawnIfApplicable()
end end
end end
for _, sweepGroup in pairs(self.sweepGroupsByName) do for groupName, sweepGroup in pairs(self.sweepGroupsByName) do
local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage)) local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then
@@ -140,7 +143,7 @@ function CapBase:SpawnIfApplicable()
end end
end end
for _, interceptGroup in pairs(self.interceptGroupsByName) do for groupName, interceptGroup in pairs(self.interceptGroupsByName) do
local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage)) local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then
@@ -151,9 +154,8 @@ end
function CapBase:CheckAndScheduleCAP() function CapBase:CheckAndScheduleCAP()
self.logger:debug("Check taskings for airbase " .. self.airbaseName) self.logger:debug("Check taskings for airbase " .. self.airbaseName)
---@type table<string, number>
local countPerStage = {} local countPerStage = {}
---@type table<string, number>
local requiredPerStage = {} local requiredPerStage = {}
local airbase = Airbase.getByName(self.airbaseName) local airbase = Airbase.getByName(self.airbaseName)
@@ -168,7 +170,7 @@ function CapBase:CheckAndScheduleCAP()
if group:IsBackup() == true then if group:IsBackup() == true then
local state = group:GetState() local state = group:GetState()
if state == "InTransit" or state == "OnStation" or state == "RtbInTen" then if state == "InTransit" or state == "OnStation" or state == "RtbInTen" then
local supposedTargetZoneID = group:GetZoneIDWhenStageID(activeStageID) local supposedTargetZoneID = group:GetZoneIDWhenStageID(activeStageID)
local currentTargetZone = group:GetCurrentTargetZoneID() local currentTargetZone = group:GetCurrentTargetZoneID()
@@ -190,7 +192,7 @@ function CapBase:CheckAndScheduleCAP()
end end
end end
end end
if countPerStage[supposedTargetZoneID] == nil then if countPerStage[supposedTargetZoneID] == nil then
countPerStage[supposedTargetZoneID] = 0 countPerStage[supposedTargetZoneID] = 0
end end
@@ -229,7 +231,7 @@ function CapBase:CheckAndScheduleCAP()
countPerStage[supposedZone] = countPerStage[supposedZone] + 1 countPerStage[supposedZone] = countPerStage[supposedZone] + 1
end end
elseif state == "InTransit" or state == "OnStation" then elseif state == "InTransit" or state == "OnStation" then
if supposedZone ~= group:GetCurrentTargetZoneID() then if supposedZone ~= group:GetCurrentTargetZoneID() then
if countPerStage[supposedZone] < requiredPerStage[supposedZone] then if countPerStage[supposedZone] < requiredPerStage[supposedZone] then
local triggerZone = self.database:GetCapZoneForZoneID(supposedZone) local triggerZone = self.database:GetCapZoneForZoneID(supposedZone)
@@ -271,7 +273,7 @@ function CapBase:CheckAndScheduleCAP()
if triggerZone then if triggerZone then
group:SendToZone(triggerZone, supposedZone, airbase) group:SendToZone(triggerZone, supposedZone, airbase)
end end
countPerStage[supposedZone] = countPerStage[supposedZone] + 1 countPerStage[supposedZone] = countPerStage[supposedZone] + 1
end end
end end
@@ -310,7 +312,8 @@ function CapBase:CheckAndScheduleIntercept()
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName) self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
---@type table<string, boolean> local interceptZoneIDs = {}
local interceptZoneIDs = {} local interceptZoneIDs = {}
local airbase = Airbase.getByName(self.airbaseName) local airbase = Airbase.getByName(self.airbaseName)
@@ -318,7 +321,7 @@ function CapBase:CheckAndScheduleIntercept()
return nil return nil
end end
for _, group in pairs(self.interceptGroupsByName) do for name, group in pairs(self.interceptGroupsByName) do
local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage)) local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
if targetZoneID then if targetZoneID then
interceptZoneIDs[targetZoneID] = true interceptZoneIDs[targetZoneID] = true
@@ -354,7 +357,7 @@ function CapBase:CheckAndScheduleIntercept()
end end
end end
end end
---RATIO. Amount of enemy fighters required per zone before another group gets added. ---RATIO. Amount of enemy fighters required per zone before another group gets added.
local ratio = 4 -- Ratio of units to intercept per zone, can be adjusted local ratio = 4 -- Ratio of units to intercept per zone, can be adjusted
@@ -376,6 +379,7 @@ function CapBase:CheckAndScheduleIntercept()
if total < required then if total < required then
for _, group in pairs(self.interceptGroupsByName) do for _, group in pairs(self.interceptGroupsByName) do
if total < required then if total < required then
local zoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
if group:GetState() == "ReadyOnTheRamp" then if group:GetState() == "ReadyOnTheRamp" then
group:SendToInterceptUnits(targets, name, airbase) group:SendToInterceptUnits(targets, name, airbase)
total = total + 1 total = total + 1
+9 -6
View File
@@ -6,19 +6,21 @@ local CapAirbase = require("classes.capClasses.CapAirbase")
---@class GlobalCapManager ---@class GlobalCapManager
local GlobalCapManager = {} local GlobalCapManager = {}
do do
---@type table<string, CapBase[]>
local airbasesPerStage = {} local airbasesPerStage = {}
---@type table<string, CapBase>
local allAirbasesByName = {} local allAirbasesByName = {}
local activeAirbasesPerActiveStage = {}
local unitsPerzonePerStage = {}
local initiated = false local initiated = false
---comment ---comment
---@param database Database ---@param database Database
---@param capConfig table ---@param capConfig table
---@param stageConfig StageConfig
---@param detectionManager DetectionManager ---@param detectionManager DetectionManager
---@param logLevel LogLevel ---@param logLevel LogLevel
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
function GlobalCapManager.start(database, capConfig, detectionManager, logLevel, spawnManager) function GlobalCapManager.start(database, capConfig, detectionManager, stageConfig, logLevel, spawnManager)
if initiated == true then return end if initiated == true then return end
local logger = Logger.new("AirbaseManager", logLevel) local logger = Logger.new("AirbaseManager", logLevel)
@@ -27,7 +29,7 @@ do
local zones = database:getStagezoneNames() local zones = database:getStagezoneNames()
if zones then if zones then
for _, stageName in pairs(zones) do for key, stageName in pairs(zones) do
if airbasesPerStage[stageName] == nil then if airbasesPerStage[stageName] == nil then
airbasesPerStage[stageName] = {} airbasesPerStage[stageName] = {}
end end
@@ -37,8 +39,9 @@ do
for _, airbaseName in pairs(airbaseNames) do for _, airbaseName in pairs(airbaseNames) do
if airbaseName then if airbaseName then
local airbaseSpecificLogger = Logger.new("CAP_" .. airbaseName, logLevel) local airbaseSpecificLogger = Logger.new("CAP_" .. airbaseName, logLevel)
local airbase = CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, runwayBombingTracker, detectionManager, spawnManager) local airbase = CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
if airbase then if airbase then
table.insert(airbasesPerStage[stageName], airbase) table.insert(airbasesPerStage[stageName], airbase)
allAirbasesByName[airbaseName] = airbase allAirbasesByName[airbaseName] = airbase
+10 -13
View File
@@ -1,11 +1,12 @@
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local RTBMission = require("classes.capClasses.taskings.RTB") local RTBMission = require("classes.capClasses.taskings.RTB")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
local GlobalConfig = require("classes.configuration.GlobalConfig") local GlobalConfig = require("classes.configuration.GlobalConfig")
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing") local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
---@class AirGroup : OnUnitLostListener, OnGroupRTBListener, OnGroupRTBInTenListener, OnLandEventListener, OnGroupOnStationListener ---@class AirGroup : OnUnitLostListener
---@field protected _logger Logger ---@field protected _logger Logger
---@field protected _groupName string ---@field protected _groupName string
---@field protected _groupType AirGroupType ---@field protected _groupType AirGroupType
@@ -80,7 +81,7 @@ function AirGroup:SetMission(mission)
end end
end end
local setMissionDelayed = function(data, _) local setMissionDelayed = function(data, time)
data.self:SetMissionPrivate(data.mission) data.self:SetMissionPrivate(data.mission)
end end
@@ -107,9 +108,8 @@ function AirGroup:SetMissionPrivate(mission)
local points = {} local points = {}
if mission and mission.params and mission.params.route and mission.params.route.points then if mission and mission.params and mission.params.route and mission.params.route.points then
local routePoints = mission.params.route.points --[[@as Array<Vec2> ]] for _, wp in pairs(mission.params.route.points) do
for _, wp in pairs(routePoints) do if wp.x and wp.y then
if wp and wp.x and wp.y then
table.insert(points, { x = wp.x, y = wp.y }) table.insert(points, { x = wp.x, y = wp.y })
end end
end end
@@ -199,8 +199,6 @@ function AirGroup:SpawnInternal(force, withoutLoadout)
end end
---@param selfA AirGroup ---@param selfA AirGroup
---@param time number
---@return number?
local function CheckLivenessTask(selfA, time) local function CheckLivenessTask(selfA, time)
local interval = selfA:CheckLiveness() local interval = selfA:CheckLiveness()
if not interval then return end if not interval then return end
@@ -266,7 +264,6 @@ function AirGroup:OnLastUnitLanded()
---@param data CheckGroupForRestartData ---@param data CheckGroupForRestartData
---@param time number ---@param time number
---@return number?
local checkGroupForRestart = function(data, time) local checkGroupForRestart = function(data, time)
local group = Group.getByName(data.self:GetName()) local group = Group.getByName(data.self:GetName())
if not group then if not group then
@@ -360,7 +357,7 @@ do --- RESPAWN FUNCTIONS
self:SetState("Dead") self:SetState("Dead")
---@param selfA AirGroup ---@param selfA AirGroup
local respawnTask = function(selfA, _) local respawnTask = function(selfA, time)
selfA:StartRepair() selfA:StartRepair()
end end
@@ -379,7 +376,8 @@ do --- RESPAWN FUNCTIONS
end end
---comment ---comment
local rearmTask = function(_, _) ---@param selfA AirGroup
local rearmTask = function(selfA, time)
self:StartRearm() self:StartRearm()
end end
@@ -404,7 +402,7 @@ do --- RESPAWN FUNCTIONS
end end
---@param selfA AirGroup ---@param selfA AirGroup
local rearmTask = function(selfA, _) local rearmTask = function(selfA, time)
selfA:MarkRearmComplete() selfA:MarkRearmComplete()
end end
@@ -417,7 +415,6 @@ end
do --EVENT LISTENERS do --EVENT LISTENERS
---@param unit Unit ---@param unit Unit
function AirGroup:OnUnitLost(unit) function AirGroup:OnUnitLost(unit)
if unit == nil then return end
self:CheckLiveness() self:CheckLiveness()
end end
@@ -437,7 +434,7 @@ do --EVENT LISTENERS
end end
end end
function AirGroup:OnUnitLanded(_, _) function AirGroup:OnUnitLanded(unit, airbase)
local anyInAir = false local anyInAir = false
local group = Group.getByName(self._groupName) local group = Group.getByName(self._groupName)
if group then if group then
@@ -34,10 +34,9 @@ function CapGroup:IsBackup()
return self._isBackup return self._isBackup
end end
---@param stageID string
---@return string? ---@return string?
function CapGroup:GetZoneIDWhenStageID(stageID) function CapGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[tostring(stageID)] return self._targetZoneIdPerStage[stageID]
end end
---@return string? ---@return string?
@@ -104,17 +103,17 @@ function CapGroup:InitWithName(groupName)
return return
end end
local subsplit = Util.split_string(configPart, "|") local subsplit = Util.split_string(configPart, "|")
if subsplit then if subsplit then
for _, value in pairs(subsplit) do for key, value in pairs(subsplit) do
local keySplit = Util.split_string(value, "]") local keySplit = Util.split_string(value, "]")
local targetZone = keySplit[2] local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1]) local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",") local commaSeperated = Util.split_string(allActives, ",")
for _, childValue in pairs(commaSeperated) do for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-") local dashSeperated = Util.split_string(value, "-")
if Util.tableLength(dashSeperated) > 1 then if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1]) local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2]) local till = tonumber(dashSeperated[2])
@@ -60,7 +60,6 @@ end
---comment ---comment
---@param units Array<string> ---@param units Array<string>
---@param zoneName string
---@param homeAirbase Airbase ---@param homeAirbase Airbase
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase) function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
@@ -69,10 +68,9 @@ function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
self:SetTargetUnits(units) self:SetTargetUnits(units)
end end
---@param stageID string
---@return string? ---@return string?
function InterceptGroup:GetZoneIDWhenStageID(stageID) function InterceptGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[tostring(stageID)] return self._targetZoneIdPerStage[stageID]
end end
---@return string? ---@return string?
@@ -93,9 +91,6 @@ function InterceptGroup:SetTargetUnits(unitNames)
end) end)
end end
---@param selfA InterceptGroup
---@param time number
---@return number?
local updateContinous = function(selfA, time) local updateContinous = function(selfA, time)
local next = selfA:UpdateTask() local next = selfA:UpdateTask()
if next then if next then
@@ -107,7 +102,6 @@ function InterceptGroup:SetTargetUnits(unitNames)
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30) self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
end end
---@param unit Unit
function InterceptGroup:RemoveTargetUnit(unit) function InterceptGroup:RemoveTargetUnit(unit)
if not unit then return end if not unit then return end
@@ -196,7 +190,7 @@ function InterceptGroup:UpdateTask()
self:SendRTB(self._airbase) self:SendRTB(self._airbase)
return nil -- Return to base if bingo fuel return nil -- Return to base if bingo fuel
end end
if selfDetected and self:IsInAir() == true then if selfDetected and self:IsInAir() == true then
if self._currentTargetName and self._currentTargetName == closestUnit:getName() then if self._currentTargetName and self._currentTargetName == closestUnit:getName() then
local distance = Util.VectorDistance3d(closestUnit:getPoint(), groupPoint) local distance = Util.VectorDistance3d(closestUnit:getPoint(), groupPoint)
@@ -219,6 +213,8 @@ function InterceptGroup:UpdateTask()
self._currentTargetName = nil self._currentTargetName = nil
end end
local speed = self._config:getMaxSpeed() local speed = self._config:getMaxSpeed()
local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit) local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit)
@@ -226,8 +222,7 @@ function InterceptGroup:UpdateTask()
return 30 -- Return to base if no intercept point could be calculated return 30 -- Return to base if no intercept point could be calculated
end end
---@type table local mission = nil
local mission
if self:IsInAir() == true then if self:IsInAir() == true then
-- If the group is in the air, create an intercept mission -- If the group is in the air, create an intercept mission
mission = INTERCEPT.getMissionFromInAir( mission = INTERCEPT.getMissionFromInAir(
@@ -328,13 +323,13 @@ function InterceptGroup:InitWithName(groupName)
configPart = string.sub(configPart, 2, #configPart) configPart = string.sub(configPart, 2, #configPart)
local subsplit = Util.split_string(configPart, "|") local subsplit = Util.split_string(configPart, "|")
if subsplit then if subsplit then
for _, value in pairs(subsplit) do for key, value in pairs(subsplit) do
local keySplit = Util.split_string(value, "]") local keySplit = Util.split_string(value, "]")
local targetZone = keySplit[2] local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1]) local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",") local commaSeperated = Util.split_string(allActives, ",")
for _, childValue in pairs(commaSeperated) do for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-") local dashSeperated = Util.split_string(value, "-")
if Util.tableLength(dashSeperated) > 1 then if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1]) local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2]) local till = tonumber(dashSeperated[2])
@@ -4,7 +4,7 @@ local Util = require("classes.util.Util")
local MissionEditorWarner = require("classes.util.MissionEditorWarnings") local MissionEditorWarner = require("classes.util.MissionEditorWarnings")
---@class SweepGroup : AirGroup ---@class SweepGroup : AirGroup
---@field _targetZoneIdPerStage table<string, string> ---@field _targetZoneIdPerStage table<number, string>
---@field _currentTargetZoneID string? ---@field _currentTargetZoneID string?
local SweepGroup = {} local SweepGroup = {}
SweepGroup.__index = SweepGroup SweepGroup.__index = SweepGroup
@@ -26,10 +26,9 @@ function SweepGroup.New(groupName, config, logger, spawnManager)
return self return self
end end
---@param stageID string
---@return string? ---@return string?
function SweepGroup:GetZoneIDWhenStageID(stageID) function SweepGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[tostring(stageID)] return self._targetZoneIdPerStage[stageID]
end end
---@return string? ---@return string?
@@ -42,7 +41,7 @@ end
---@field self SweepGroup ---@field self SweepGroup
---@param params SetTaskParams ---@param params SetTaskParams
local setMissionDelayedTask = function(params, _) local setMissionDelayedTask = function(params, time)
params.self:SetMissionPrivate(params.task) params.self:SetMissionPrivate(params.task)
end end
@@ -51,6 +50,8 @@ function SweepGroup:SendToZone(zone, targetZoneID, airbase)
self._currentTargetZoneID = targetZoneID self._currentTargetZoneID = targetZoneID
local group = Group.getByName(self._groupName)
local mission = SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config) local mission = SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
if mission then if mission then
---@type SetTaskParams ---@type SetTaskParams
@@ -78,16 +79,16 @@ function SweepGroup:InitWithName(groupName)
local configPart = split_string[2] local configPart = split_string[2]
configPart = string.sub(configPart, 2, #configPart) configPart = string.sub(configPart, 2, #configPart)
local subsplit = Util.split_string(configPart, "|") local subsplit = Util.split_string(configPart, "|")
if subsplit then if subsplit then
for _, value in pairs(subsplit) do for key, value in pairs(subsplit) do
local keySplit = Util.split_string(value, "]") local keySplit = Util.split_string(value, "]")
local targetZone = keySplit[2] local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1]) local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",") local commaSeperated = Util.split_string(allActives, ",")
for _, childValue in pairs(commaSeperated) do for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-") local dashSeperated = Util.split_string(value, "-")
if Util.tableLength(dashSeperated) > 1 then if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1]) local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2]) local till = tonumber(dashSeperated[2])
@@ -8,7 +8,6 @@ DetectionManager.__index = DetectionManager
---@param logger Logger ---@param logger Logger
---@return DetectionManager
function DetectionManager.New(logger) function DetectionManager.New(logger)
local self = setmetatable({}, DetectionManager) local self = setmetatable({}, DetectionManager)
@@ -25,7 +24,6 @@ function DetectionManager.New(logger)
---@param selfA DetectionManager ---@param selfA DetectionManager
---@param time number ---@param time number
---@return number?
local updateDetectingUnitsTask = function(selfA, time) local updateDetectingUnitsTask = function(selfA, time)
selfA:UpdateDetectingUnits() selfA:UpdateDetectingUnits()
return time + 120 return time + 120
@@ -34,19 +32,18 @@ function DetectionManager.New(logger)
---@param selfA DetectionManager ---@param selfA DetectionManager
---@param time number ---@param time number
---@return number?
local updateDetected = function(selfA, time) local updateDetected = function(selfA, time)
selfA:UpdateDetectedUnits() selfA:UpdateDetectedUnits()
return time + 10 return time + 10
end end
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130) timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
return self return self
end end
---@param unitName string ---@param unitName string
---@param coalitionSide CoalitionSide ---@param coalitionSide CoalitionSide
---@return boolean
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide) function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
local coalitionString = tostring(coalitionSide) local coalitionString = tostring(coalitionSide)
if not self._detectedUnits[coalitionString] then if not self._detectedUnits[coalitionString] then
@@ -60,8 +57,7 @@ function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20 return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
end end
---@param coalitionSide CoalitionSide ---@return Array<string>
---@return Array<string> detectedUnits
function DetectionManager:GetDetectedUnitsBy(coalitionSide) function DetectionManager:GetDetectedUnitsBy(coalitionSide)
local coalitionString = tostring(coalitionSide) local coalitionString = tostring(coalitionSide)
if not self._detectedUnits[coalitionString] then if not self._detectedUnits[coalitionString] then
@@ -21,7 +21,7 @@ end
---comment ---comment
---@param weapon Weapon ---@param weapon Weapon
function RunwayBombingTracker:OnWeaponFired(_, weapon, _) function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
if weapon == nil then if weapon == nil then
return return
@@ -30,7 +30,7 @@ function RunwayBombingTracker:OnWeaponFired(_, weapon, _)
local desc = weapon:getDesc() local desc = weapon:getDesc()
local isTrackable = desc.category == Weapon.Category.BOMB or (desc.category == Weapon.Category.MISSILE and desc.missileCategory == Weapon.MissileCategory.CRUISE) local isTrackable = desc.category == Weapon.Category.BOMB or (desc.category == Weapon.Category.MISSILE and desc.missileCategory == Weapon.MissileCategory.CRUISE)
if isTrackable == true then if isTrackable == true then
---@type WeaponTrackingArgs ---@type WeaponTrackingArgs
local weaponTrackingArgs = { local weaponTrackingArgs = {
weapon = weapon, weapon = weapon,
@@ -56,14 +56,12 @@ end
---@field self RunwayBombingTracker ---@field self RunwayBombingTracker
---@private ---@private
---@param time number
---@param weaponTrackingArgs WeaponTrackingArgs ---@param weaponTrackingArgs WeaponTrackingArgs
---@return number?
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time) function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
local weapon = weaponTrackingArgs.weapon local weapon = weaponTrackingArgs.weapon
local self = weaponTrackingArgs.self local self = weaponTrackingArgs.self
if not weapon or weapon:isExist() == false then return nil end if not weapon or weapon:isExist() == false then return nil end
local pos = weapon:getPoint() local pos = weapon:getPoint()
@@ -85,7 +83,11 @@ function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
x = pos.x + velocity.x * nextInterval, x = pos.x + velocity.x * nextInterval,
y = pos.z + velocity.z * nextInterval y = pos.z + velocity.z * nextInterval
} }
self:OnWeaponImpact(weapon:getDesc(), impactPoint) self:OnWeaponImpact(weapon:getDesc(), impactPoint)
return nil return nil
end end
@@ -104,10 +106,11 @@ function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
self._logger:debug("RunwayBombingTracker:OnWeaponImpact") self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
local warhead = weaponDesc.warhead --[[@as table]] local warhead = weaponDesc.warhead
local explosiveMass = (warhead.explosiveMass or warhead.shapedExplosiveMass) --[[@as number]] local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass
for runway, strikeMission in pairs(self.trackedRunways) do
for _, strikeMission in pairs(self.trackedRunways) do
local zone= strikeMission:GetRunwayZone() local zone= strikeMission:GetRunwayZone()
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
+3 -8
View File
@@ -7,7 +7,6 @@ local CAP = {}
---@param attackHelos boolean ---@param attackHelos boolean
---@return table ---@return table
local function GetCAPTargetTypes(attackHelos) local function GetCAPTargetTypes(attackHelos)
---@type Array<string>
local targetTypes = { local targetTypes = {
[1] = "Planes", [1] = "Planes",
} }
@@ -34,6 +33,9 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
local furthestA = nil local furthestA = nil
local furthestB = nil local furthestB = nil
local furthestFromBase = nil
local furthestFromBaseDistance = 0
local furthestDistance = 0 local furthestDistance = 0
for indexA, pointA in ipairs(capZone.verts) do for indexA, pointA in ipairs(capZone.verts) do
@@ -54,10 +56,7 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
---@type Vec2 ---@type Vec2
local baseVec2 = { x = baseVec3.x, y = baseVec3.z } local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
---@type Vec2
local pointA = capZone.verts[furthestA] local pointA = capZone.verts[furthestA]
---@type Vec2
local pointB = capZone.verts[furthestB] local pointB = capZone.verts[furthestB]
local furthest = pointA local furthest = pointA
local closest = pointB local closest = pointB
@@ -88,7 +87,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
local GetOutboundTask = function(airbase, capZone, capConfig) local GetOutboundTask = function(airbase, capZone, capConfig)
local airbaseVec3 = airbase:getPoint() local airbaseVec3 = airbase:getPoint()
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z } local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
@@ -130,7 +128,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig) function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
local points = { local points = {
[1] = GetOutboundTask(airbase, capZone, capConfig), [1] = GetOutboundTask(airbase, capZone, capConfig),
@@ -158,7 +155,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
function CAP.getAsMission(groupName, airbase, capZone, capConfig) function CAP.getAsMission(groupName, airbase, capZone, capConfig)
local points = { local points = {
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig), [1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
@@ -185,7 +181,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
function CAP.getAsTasking(groupName, airbase, capZone, capConfig) function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500 local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
@@ -11,7 +11,6 @@ local INTERCEPT = {}
---@param speed number? ---@param speed number?
---@param alt number? ---@param alt number?
---@param config CapConfig ---@param config CapConfig
---@return table
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt) function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
local airbaseVec3 = airbase:getPoint() local airbaseVec3 = airbase:getPoint()
@@ -39,7 +38,7 @@ function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, con
id = 'Mission', id = 'Mission',
params = { params = {
airborne = true, airborne = true,
route = { route = {
points = points points = points
} }
} }
@@ -93,7 +92,7 @@ end
---@param currentPoint Vec2 ---@param currentPoint Vec2
---@param targetPosition Vec2 ---@param targetPosition Vec2
---@param targetUnit Unit ---@param targetUnit Unit
---@param airbase Airbase ---@param airbase any
---@param config any ---@param config any
---@param speed number? ---@param speed number?
---@param alt number? ---@param alt number?
@@ -160,10 +159,10 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
params = { params = {
tasks = { tasks = {
id = 'EngageTargetsInZone', id = 'EngageTargetsInZone',
params = { params = {
point = targetPoint, point = targetPoint,
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone. zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
targetTypes = { targetTypes = {
[1] = "Planes", [1] = "Planes",
}, },
priority = 0 priority = 0
@@ -189,10 +188,10 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
params = { params = {
tasks = { tasks = {
id = 'EngageTargetsInZone', id = 'EngageTargetsInZone',
params = { params = {
point = targetPoint, point = targetPoint,
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone. zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
targetTypes = { targetTypes = {
[1] = "Planes", [1] = "Planes",
}, },
priority = 0 priority = 0
+3 -8
View File
@@ -7,7 +7,6 @@ local RTB = {}
---@param airbase Airbase ---@param airbase Airbase
---@param missionPoint Vec2 ---@param missionPoint Vec2
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
function RTB.getAsMission(airbase, missionPoint, capConfig) function RTB.getAsMission(airbase, missionPoint, capConfig)
return { return {
id = "Mission", id = "Mission",
@@ -48,7 +47,7 @@ local getRunwayIntoWindCourseRad = function(airbase)
else else
rad = 0 - rad rad = 0 - rad
end end
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0} local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
local alignment = Util.vectorAlignment(windVec, runwayVec) local alignment = Util.vectorAlignment(windVec, runwayVec)
@@ -61,14 +60,14 @@ local getRunwayIntoWindCourseRad = function(airbase)
do --inverse do --inverse
local degree = math.deg(runway.course) local degree = math.deg(runway.course)
degree = (degree + 180) % 360 degree = (degree + 180) % 360
local rad = math.rad(degree) local rad = math.rad(degree)
if rad < 0 then if rad < 0 then
rad = math.abs(rad) rad = math.abs(rad)
else else
rad = 0 - rad rad = 0 - rad
end end
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0} local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
local alignment = Util.vectorAlignment(windVec, runwayVec) local alignment = Util.vectorAlignment(windVec, runwayVec)
@@ -114,7 +113,6 @@ end
---comment ---comment
---@param airbase Airbase ---@param airbase Airbase
---@param missionPoint Vec2 ---@param missionPoint Vec2
---@return table
---@param capConfig CapConfig ---@param capConfig CapConfig
function RTB.getApproachPoint(airbase, missionPoint, capConfig) function RTB.getApproachPoint(airbase, missionPoint, capConfig)
@@ -149,7 +147,6 @@ function RTB.getApproachPoint(airbase, missionPoint, capConfig)
end end
---@param airbase Airbase ---@param airbase Airbase
---@return table
function RTB.getInitialPoint(airbase) function RTB.getInitialPoint(airbase)
local point = calcInitialPoint(airbase) local point = calcInitialPoint(airbase)
return { return {
@@ -173,8 +170,6 @@ function RTB.getInitialPoint(airbase)
} }
end end
---@param airbase Airbase
---@return table
function RTB.getLandingPoint(airbase) function RTB.getLandingPoint(airbase)
local basePoint = airbase:getPoint() local basePoint = airbase:getPoint()
return { return {
@@ -7,7 +7,6 @@ local SWEEP = {}
---@param attackHelos boolean ---@param attackHelos boolean
---@return table ---@return table
local function GetCAPTargetTypes(attackHelos) local function GetCAPTargetTypes(attackHelos)
---@type Array<string>
local targetTypes = { local targetTypes = {
[1] = "Planes", [1] = "Planes",
} }
@@ -50,9 +49,7 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
---@type Vec2 ---@type Vec2
local baseVec2 = { x = baseVec3.x, y = baseVec3.z } local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
---@type Vec2
local pointA = capZone.verts[furthestA] local pointA = capZone.verts[furthestA]
---@type Vec2
local pointB = capZone.verts[furthestB] local pointB = capZone.verts[furthestB]
local furthest = pointA local furthest = pointA
local closest = pointB local closest = pointB
@@ -83,7 +80,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
local GetOutboundTask = function(airbase, capZone, capConfig) local GetOutboundTask = function(airbase, capZone, capConfig)
local airbaseVec3 = airbase:getPoint() local airbaseVec3 = airbase:getPoint()
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z } local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
@@ -125,7 +121,6 @@ end
---@param airbase Airbase ---@param airbase Airbase
---@param capZone SpearheadTriggerZone ---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig ---@param capConfig CapConfig
---@return table
function SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig) function SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig) local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
-4
View File
@@ -21,10 +21,6 @@ function CapConfig.new()
if SpearheadConfig == nil then SpearheadConfig = {} end if SpearheadConfig == nil then SpearheadConfig = {} end
if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end
if type(SpearheadConfig.CapConfig.enabled) ~= "boolean" then
SpearheadConfig.CapConfig.enabled = true
end
local enabled = SpearheadConfig.CapConfig.enabled local enabled = SpearheadConfig.CapConfig.enabled
if enabled == nil then enabled = true end if enabled == nil then enabled = true end
self._isEnabled = enabled self._isEnabled = enabled
@@ -1,4 +1,8 @@
local briefingMessageTime = nil
---@class GlobalConfig ---@class GlobalConfig
---@field private _briefingTime number ---@field private _briefingTime number
---@field private _debugEnabled boolean ---@field private _debugEnabled boolean
+1 -1
View File
@@ -18,7 +18,7 @@ local _logger = Logger.new("StageConfig", Logger.LogLevel)
---@return StageConfig ---@return StageConfig
local function new() local function new()
if SpearheadConfig == nil then if SpearheadConfig == nil then
_logger:warn("SpearheadConfig is nil, creating default SpearheadConfig") _logger:warn("SpearheadConfig is nil, creating default SpearheadConfig")
SpearheadConfig = {} SpearheadConfig = {}
end end
+2 -1
View File
@@ -1,4 +1,5 @@
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local Util = require("classes.util.Util")
local StageRepository = require("classes.stageClasses.StageRepository") local StageRepository = require("classes.stageClasses.StageRepository")
---@class DebugMenu ---@class DebugMenu
@@ -21,7 +22,7 @@ function DebugMenu:RegisterMenus()
missionCommands.addSubMenu(menuName, {}) missionCommands.addSubMenu(menuName, {})
local refresh = function(params) local refresh = function(params)
local selfA = params.self --[[@as DebugMenu]] local selfA = params.self
selfA:RefreshMenu() selfA:RefreshMenu()
end end
missionCommands.addCommand("Refresh Menu", debugMenuPath, refresh, { self = self }) missionCommands.addCommand("Refresh Menu", debugMenuPath, refresh, { self = self })
+19
View File
@@ -0,0 +1,19 @@
do -- mission aliases
--- @alias LogLevel
--- | "DEBUG"
--- | "INFO"
--- | "WARN"
--- | "ERROR"
--- | "NONE"
---@class Array<T>: { [integer]: T }
end
---@class KeyValuePair
---@field key string
---@field value any
+42 -54
View File
@@ -4,43 +4,37 @@ local MissionEditorWarning = require("classes.util.MissionEditorWarnings")
local RouteUtil = require("classes.spearhead_routeutil") local RouteUtil = require("classes.spearhead_routeutil")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
---@class FleetGroup : OnStageChangedListener ---@class FleetGroup
---@field private fleetGroupName string
---@field private logger Logger
---@field private fleetNameIdentifier string
---@field private targetZonePerStage table<string, string>
---@field private currentTargetZone string
---@field private pointsPerZone table<string, {pointA: Vec3, pointB: Vec3}>
local FleetGroup = {} local FleetGroup = {}
FleetGroup.__index = FleetGroup
---comment ---comment
---@param fleetGroupName string ---@param fleetGroupName string
---@param database Database ---@param database Database
---@param logger Logger ---@param logger Logger
---@return FleetGroup? ---@return nil
function FleetGroup.new(fleetGroupName, database, logger) function FleetGroup:new(fleetGroupName, database, logger)
local o = {}
local self = setmetatable({}, FleetGroup) setmetatable(o, { __index = self })
self.fleetGroupName = fleetGroupName o.fleetGroupName = fleetGroupName
self.logger = logger o.logger = logger
local split_name = Util.split_string(fleetGroupName, "_") local split_name = Util.split_string(fleetGroupName, "_")
if Util.tableLength(split_name) < 2 then if Util.tableLength(split_name) < 2 then
MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>") MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>")
return nil return nil
end end
self.fleetNameIdentifier = split_name[2] o.fleetNameIdentifier = split_name[2]
self.targetZonePerStage = {} o.targetZonePerStage = {}
self.currentTargetZone = nil o.currentTargetZone = nil
self.pointsPerZone = {} o.pointsPerZone = {}
do --INIT do --INIT
local carrierRouteZones = database:getCarrierRouteZones() local carrierRouteZones = database:getCarrierRouteZones()
for _, zoneName in pairs(carrierRouteZones) do for _, zoneName in pairs(carrierRouteZones) do
if Util.strContains(string.lower(zoneName), "_".. string.lower(self.fleetNameIdentifier) .. "_" ) == true then if Util.strContains(string.lower(zoneName), "_".. string.lower(o.fleetNameIdentifier) .. "_" ) == true then
local zone = DcsUtil.getZoneByName(zoneName) local zone = DcsUtil.getZoneByName(zoneName)
if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then
local split_string = Util.split_string(zoneName, "_") local split_string = Util.split_string(zoneName, "_")
@@ -49,18 +43,18 @@ function FleetGroup.new(fleetGroupName, database, logger)
"CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName) "CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName)
else else
---@param zoneA SpearheadTriggerZone ---@param zone SpearheadTriggerZone
---@return Vec2, Vec2 ---@return Vec2, Vec2
local function GetTwoFurthestPoints(zoneA) local function GetTwoFurthestPoints(zone)
local biggest = nil local biggest = nil
local biggestA = zoneA.verts[1] local biggestA = zone.verts[1]
local biggestB = zoneA.verts[2] local biggestB = zone.verts[2]
for i = 1, 3 do for i = 1, 3 do
for ii = i + 1, 4 do for ii = i + 1, 4 do
local a = zoneA.verts[i] local a = zone.verts[i]
local b = zoneA.verts[ii] local b = zone.verts[ii]
local dist = Util.VectorDistance2d(a, b) local dist = Util.VectorDistance2d(a, b)
if biggest == nil or dist > biggest then if biggest == nil or dist > biggest then
@@ -106,15 +100,9 @@ function FleetGroup.new(fleetGroupName, database, logger)
local first, second = getMinMaxStage(split_string[3]) local first, second = getMinMaxStage(split_string[3])
if first ~= nil and second ~= nil then if first ~= nil and second ~= nil then
for i = first, second do for i = first, second do
self.targetZonePerStage[tostring(i)] = zoneName o.targetZonePerStage[tostring(i)] = zoneName
end end
---@type {pointA: Vec3, pointB: Vec3} o.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} }
local entry = {
pointA = {x = pointA.x, z = pointA.y, y = 0},
pointB = {x = pointB.x, z = pointB.y, y = 0}
}
self.pointsPerZone[zoneName] = entry
else else
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName) MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
end end
@@ -126,32 +114,32 @@ function FleetGroup.new(fleetGroupName, database, logger)
end end
end end
SpearheadEvents.AddStageNumberChangedListener(self) local SetTaskAsync = function(input, time)
return self local targetZone = input.targetZone
end local task = input.task
local groupName = input.groupName
local logger = input.logger
local SetTaskAsync = function(input, _) local group = Group.getByName(groupName)
local targetZone = input.targetZone --[[@as string]] if group then
local task = input.task --[[@as table]] logger:info("Sending " .. groupName .. " to " .. targetZone)
local groupName = input.groupName --[[@as string]] group:getController():setTask(task)
local l_logger = input.logger --[[@as Logger]] end
local group = Group.getByName(groupName)
if group then
l_logger:info("Sending " .. groupName .. " to " .. targetZone)
group:getController():setTask(task)
end end
end
function FleetGroup:OnStageNumberChanged(number, laneIdentifier) o.OnStageNumberChanged = function(self, number, laneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now -- only react on "main" lane changes, ignore other lanes for now
if laneIdentifier ~= nil then return end if laneIdentifier ~= nil then return end
local targetZone = self.targetZonePerStage[tostring(number)] local targetZone = self.targetZonePerStage[tostring(number)]
if targetZone and targetZone ~= self.currentTargetZone then if targetZone and targetZone ~= self.currentTargetZone then
local points = self.pointsPerZone[targetZone] local points = self.pointsPerZone[targetZone]
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB) local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5) timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
end
end end
SpearheadEvents.AddStageNumberChangedListener(o)
return o
end end
return FleetGroup return FleetGroup
@@ -17,7 +17,7 @@ GlobalFleetManager.start = function(database)
for _, groupName in pairs(all_groups) do for _, groupName in pairs(all_groups) do
if Util.startsWith(string.lower(groupName), "carriergroup" ) == true then if Util.startsWith(string.lower(groupName), "carriergroup" ) == true then
logger:info("Registering " .. groupName .. " as a managed fleet") logger:info("Registering " .. groupName .. " as a managed fleet")
local carrierGroup = FleetGroup.new(groupName, database, logger) local carrierGroup = FleetGroup:new(groupName, database, logger)
table.insert(fleetGroups, carrierGroup) table.insert(fleetGroups, carrierGroup)
end end
end end
+9 -31
View File
@@ -15,45 +15,24 @@ MizGroupsManager._groupNames = {}
MizGroupsManager._spawnTemplateData = {} MizGroupsManager._spawnTemplateData = {}
do --init do --init
for coalition_name, coalition_data in pairs(env.mission.coalition) do
---@param coalition_name string local coalition_nr = DcsUtil.stringToCoalition(coalition_name)
---@return Coalition
local getCoalitionData = function(coalition_name)
return env.mission.coalition[coalition_name]
end
---@param country Country
---@param key string
---@return Groups?
local getGroupsData = function(country, key)
if key == "planes" then return country.plane end
if key == "helicopters" then return country.helicopter end
if key == "ground" then return country.vehicle end
if key == "ships" then return country.ship end
if key == "statics" then return country.static end
return nil
end
for _, coalition_name in ipairs({"neutral", "blue", "red"}) do
local coalition_data = getCoalitionData(coalition_name)
if coalition_data.country then if coalition_data.country then
for _, country_data in pairs(coalition_data.country) do for country_index, country_data in pairs(coalition_data.country) do
for _, category_name in pairs(DcsUtil.getAllGroupCategoryNames()) do for category_name, categorydata in pairs(country_data) do
local category_id = DcsUtil.stringToGroupCategory(category_name) local category_id = DcsUtil.stringToGroupCategory(category_name)
local groups_data = getGroupsData(country_data, category_name) if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then
for group_index, group in pairs(categorydata.group) do
if category_id ~= nil and type(groups_data) == "table" and groups_data.group ~= nil and type(groups_data.group) == "table" then local name = group.name
for _, group in pairs(groups_data.group) do
local name = group.name --[[@as string]]
local skippable = false local skippable = false
local isStatic = false local isStatic = false
if category_id == DcsUtil.GroupCategory.STATIC then if category_id == DcsUtil.GroupCategory.STATIC then
isStatic = true isStatic = true
local unit = group.units[1] --[[@as table]] local unit = group.units[1]
if unit and unit.category == "Heliports" then if unit and unit.category == "Heliports" then
skippable = true skippable = true
elseif unit and unit.name then elseif unit and unit.name then
name = unit.name --[[@as string]] name = unit.name
else else
env.error("Group " .. name .. " has no units, skipping it.") env.error("Group " .. name .. " has no units, skipping it.")
skippable = true skippable = true
@@ -93,7 +72,6 @@ function MizGroupsManager.IsGroupStatic(groupName)
return nil return nil
end end
---@param groupName string
---@return SpawnData? ---@return SpawnData?
function MizGroupsManager.getSpawnTemplateData(groupName) function MizGroupsManager.getSpawnTemplateData(groupName)
return MizGroupsManager._spawnTemplateData[groupName] return MizGroupsManager._spawnTemplateData[groupName]
+25 -19
View File
@@ -16,11 +16,11 @@ SpawnManager.__index = SpawnManager
---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn ---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn
---@param logger Logger ---@param logger Logger
---@return SpawnManager
function SpawnManager.new(logger) function SpawnManager.new(logger)
local self = setmetatable({}, SpawnManager) local self = setmetatable({}, SpawnManager)
self._logger = logger self._logger = logger
self._persistedUnits = {} -- Stores units that should be persisted by name self._persistedUnits = {} -- Stores units that should be persisted by name
return self return self
@@ -33,7 +33,7 @@ end
---@return StaticObject|Group|nil spawned ---@return StaticObject|Group|nil spawned
---@return boolean isStatic ---@return boolean isStatic
function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant) function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
local spawnData = MizGroupsManager.getSpawnTemplateData(groupName) local spawnData = MizGroupsManager.getSpawnTemplateData(groupName)
if spawnData == nil then if spawnData == nil then
@@ -44,13 +44,13 @@ function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
if spawnData.isStatic == true then if spawnData.isStatic == true then
return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true
else else
return self:SpawnGroupInternal(spawnData, overrides, isGroupPersistant), false return self:SpawnGroupInternal(groupName, spawnData, overrides, isGroupPersistant), false
end end
end end
function SpawnManager:DestroyGroup(groupName) function SpawnManager:DestroyGroup(groupName)
if groupName == nil then return end if groupName == nil then return end
if self:IsGroupStatic(groupName) == true then if self:IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName) local object = StaticObject.getByName(groupName)
if object ~= nil then if object ~= nil then
@@ -103,20 +103,20 @@ function SpawnManager:OnUnitLost(object)
end end
end end
---@param groupName string
function SpawnManager:SpawnCorpsesOnly(groupName) function SpawnManager:SpawnCorpsesOnly(groupName)
if groupName == nil then return end
end end
do --- privates do --- privates
---@private ---@private
---@param groupName string
---@param spawnData SpawnData ---@param spawnData SpawnData
---@param override SpawnOverrides? ---@param override SpawnOverrides?
---@param isPersistent boolean? ---@param isPersistent boolean?
---@return Group|nil ---@return Group|nil
function SpawnManager:SpawnGroupInternal(spawnData, override, isPersistent) function SpawnManager:SpawnGroupInternal(groupName, spawnData, override, isPersistent)
if not spawnData then return end if not spawnData then return end
local country = spawnData.country local country = spawnData.country
@@ -125,31 +125,37 @@ do --- privates
end end
local spawnTemplate = Util.deepCopyTable(spawnData.groupTemplate) local spawnTemplate = Util.deepCopyTable(spawnData.groupTemplate)
---@type Array<string>
local removeableUnitNames = {}
--[[ --[[
TODO: Spawn units at "current"/LastKnown position with them going to the next waypoint. TODO: Spawn units at "current"/LastKnown position with them going to the next waypoint.
ONLY when perstable data is found. ONLY when perstable data is found.
]] ]]
if spawnTemplate and spawnTemplate["units"] then if spawnTemplate and spawnTemplate["units"] then
local units = spawnTemplate["units"] --[[@as Array<table>]] for _, unit in pairs(spawnTemplate["units"]) do
for _, unit in pairs(units) do local name = unit["name"]
local name = unit["name"] --[[@as string]]
SpearheadEvents.addOnUnitLostEventListener(name, self) SpearheadEvents.addOnUnitLostEventListener(name, self)
local state = Persistence.UnitState(name)
if state then
if state.isDead == true then
removeableUnitNames[#removeableUnitNames+1] = name
end
end
if override and override.emptyLoadouts == true then if override and override.emptyLoadouts == true then
if unit["payload"] and unit["payload"]["pylons"] then if unit["payload"] and unit["payload"]["pylons"] then
local payload = unit["payload"] --[[@as table]] unit["payload"]["pylons"] = {}
payload["pylons"] = {} --[[@as table]]
end end
end end
if unit["parking"] then if unit["parking"] then
unit["parking_landing"] = unit["parking"] --[[@as number]] unit["parking_landing"] = unit["parking"]
end end
if unit["parking_id"] then if unit["parking_id"] then
unit["parking_landing_id"] = unit["parking_id"] --[[@as number]] unit["parking_landing_id"] = unit["parking_id"]
end end
end end
@@ -163,6 +169,7 @@ do --- privates
local group = coalition.addGroup(country, spawnData.category, spawnTemplate) local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
for _, unit in pairs(group:getUnits()) do for _, unit in pairs(group:getUnits()) do
self:CheckUnitAndReplaceIfPersistentDead(unit) self:CheckUnitAndReplaceIfPersistentDead(unit)
if isPersistent == true then if isPersistent == true then
@@ -174,6 +181,7 @@ do --- privates
return group return group
end end
return nil return nil
end end
@@ -203,11 +211,9 @@ do --- privates
spawnTemplate["x"] = persistentState.pos.x spawnTemplate["x"] = persistentState.pos.x
spawnTemplate["y"] = persistentState.pos.z spawnTemplate["y"] = persistentState.pos.z
if spawnTemplate["units"] and spawnTemplate["units"][1] then if spawnTemplate["units"] and spawnTemplate["units"][1] then
local units = spawnTemplate["units"] --[[@as Array<table>]] spawnTemplate["units"][1]["x"] = persistentState.pos.x
local firstUnit = units[1] --[[@as table]] spawnTemplate["units"][1]["y"] = persistentState.pos.z
firstUnit["x"] = persistentState.pos.x --[[@as number]] spawnTemplate["units"][1]["heading"] = persistentState.heading or 0
firstUnit["y"] = persistentState.pos.z --[[@as number]]
firstUnit["heading"] = persistentState.heading or 0 --[[@as number]]
end end
end end
end end
+11 -23
View File
@@ -89,7 +89,7 @@ do
else else
logger:error("Could not load persistence file, using default tables") logger:error("Could not load persistence file, using default tables")
end end
if tables.version == nil then tables.version = version end if tables.version == nil then tables.version = version end
end end
@@ -112,9 +112,9 @@ do
logger:info("Wrote persistence data to file") logger:info("Wrote persistence data to file")
end end
local UpdateContinuous = function(_, time) local UpdateContinuous = function(null, time)
env.info("[Spearhead][Persistence] Checking up on persistence state...") env.info("[Spearhead][Persistence] Checking up on persistence state...")
if Persistence._updateRequired == true then if Persistence._updateRequired == true then
local status, result = pcall(writeToFile) local status, result = pcall(writeToFile)
if status == false then if status == false then
env.error("[Spearhead][Persistence] Could not write state to file: " .. result) env.error("[Spearhead][Persistence] Could not write state to file: " .. result)
@@ -135,15 +135,13 @@ do
end end
local warnForNonPersistenceContinous = function(_, time) local warnForNonPersistenceContinous = function(null, time)
trigger.action.outText("Persistence was enabeld, however, io and lfs are not available and no persistence will be done. Make sure to either disable persistence or fix the issues before continuing.", 10) trigger.action.outText("Persistence was enabeld, however, io and lfs are not available and no persistence will be done. Make sure to either disable persistence or fix the issues before continuing.", 10)
return time + 9 return time + 9
end end
---@param dir string @BaseDirectory ---@param dir string @BaseDirectory
---@param startsWith string ---@return string
---@param default string?
---@return string?
local getLastFileOrDefault = function(dir, startsWith, default) local getLastFileOrDefault = function(dir, startsWith, default)
local latestFile, lastNumber = default, 0 local latestFile, lastNumber = default, 0
@@ -183,14 +181,11 @@ do
local fileName = "Spearhead_Persistence.0.spearhead" local fileName = "Spearhead_Persistence.0.spearhead"
if SpearheadConfig and SpearheadConfig.Persistence then if SpearheadConfig and SpearheadConfig.Persistence then
if SpearheadConfig.Persistence.fileName then if SpearheadConfig.Persistence.fileName then
if type(SpearheadConfig.Persistence.fileName) ~= "string" then
SpearheadConfig.Persistence.fileName = "Spearhead_Persistence.0.spearhead"
end
local userFileName = SpearheadConfig.Persistence.fileName local userFileName = SpearheadConfig.Persistence.fileName
local split = Util.split_string(userFileName, ".") local split = Util.split_string(userFileName, ".")
if not split or #split < 3 then if not split and #split < 3 then
split = split or {}
split[#split+1] = "0" split[#split+1] = "0"
split[#split+1] = "spearhead" split[#split+1] = "spearhead"
end end
@@ -199,14 +194,11 @@ do
split[#split+1] = "0" split[#split+1] = "0"
split[#split+1] = "spearhead" split[#split+1] = "spearhead"
end end
fileName = table.concat(split, ".") fileName = table.concat(split, ".")
end end
if SpearheadConfig.Persistence.directory then if SpearheadConfig.Persistence.directory ~= nil then
if type(SpearheadConfig.Persistence.directory) ~= "string" then
SpearheadConfig.Persistence.directory = lfs.writedir() .. "\\Data"
end
dir = SpearheadConfig.Persistence.directory dir = SpearheadConfig.Persistence.directory
end end
end end
@@ -216,10 +208,6 @@ do
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName) local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
if lastFile == nil then
lastFile = fileName
end
local fileSplit = Util.split_string(lastFile, ".") local fileSplit = Util.split_string(lastFile, ".")
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1) fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
fileName = table.concat(fileSplit, ".") fileName = table.concat(fileSplit, ".")
@@ -230,12 +218,12 @@ do
logger:info("No previous persistence file found, using default: " .. fileName) logger:info("No previous persistence file found, using default: " .. fileName)
end end
logger:info("New Persistence file name: " .. tostring(fileName)) logger:info("New Persistence file name: " .. tostring(fileName))
local lastPath = dir .. "\\" .. lastFile local lastPath = dir .. "\\" .. lastFile
local path = dir .. "\\" .. fileName local path = dir .. "\\" .. fileName
Persistence._path = path Persistence._path = path
createFileIfNotExists(path) createFileIfNotExists(path)
loadTablesFromFile(lastPath) loadTablesFromFile(lastPath)
timer.scheduleFunction(UpdateContinuous, nil, timer.getTime() + 120) timer.scheduleFunction(UpdateContinuous, nil, timer.getTime() + 120)
+29 -42
View File
@@ -122,7 +122,7 @@ function Database.New(Logger)
self._logger:debug("Initiating tables") self._logger:debug("Initiating tables")
do -- INIT ZONE TABLES do -- INIT ZONE TABLES
for _, zone_data in pairs(DcsUtil.__trigger_zones) do for zone_ind, zone_data in pairs(DcsUtil.__trigger_zones) do
local zone_name = zone_data.name local zone_name = zone_data.name
---@type Vec2 ---@type Vec2
@@ -209,18 +209,15 @@ function Database.New(Logger)
self._logger:debug("initiated zone tables, continuing with descriptions") self._logger:debug("initiated zone tables, continuing with descriptions")
do --load markers do --load markers
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
if Util.startsWith(string.lower(layer_object.name), "buildable", true) == true then if Util.startsWith(string.lower(layer_object.name), "buildable", true) == true then
local airbaseData = self:getAirbaseDataForDrawLayer(layer_object) local airbaseData = self:getAirbaseDataForDrawLayer(layer_object)
if airbaseData then if airbaseData then
self._logger:debug("found airbase data for " .. layer_object.name) self._logger:debug("found airbase data for " .. layer_object.name)
if layer_object.primitiveType == "TextBox" then local number = tonumber(layer_object.text)
layer_object = layer_object --[[@as TextBox]] airbaseData.buildingKilos = number
local number = tonumber(layer_object.text)
airbaseData.buildingKilos = number
end
end end
end end
end end
@@ -232,9 +229,9 @@ function Database.New(Logger)
---@type table<integer, boolean> ---@type table<integer, boolean>
do -- custom drawings do -- custom drawings
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
if Util.startsWith(layer_object.name, "drawing_", true) then if Util.startsWith(layer_object.name, "drawing_", true) then
local object = layer_object --[[@as DrawingObject]] local object = layer_object --[[@as DrawingObject]]
local stageDrawing = StageDrawing.New(object) local stageDrawing = StageDrawing.New(object)
@@ -260,19 +257,16 @@ function Database.New(Logger)
-- Find and add Briefing -- Find and add Briefing
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
if Util.startsWith(string.lower(layer_object.name), "stagebriefing_", true) == true then if Util.startsWith(string.lower(layer_object.name), "stagebriefing_", true) == true then
local zone = DcsUtil.getZoneByName(stageZoneName) local zone = DcsUtil.getZoneByName(stageZoneName)
local vec2 = { x = layer_object.mapX, y = layer_object.mapY } local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if zone and Util.is2dPointInZone(vec2, zone) == true then if zone and Util.is2dPointInZone(vec2, zone) == true then
if layer_object.primitiveType == "TextBox" then local description = layer_object.text
layer_object = layer_object --[[@as TextBox]] if description and description ~= "" then
local description = layer_object.text stageData.StageBriefing = description
if description and description ~= "" then
stageData.StageBriefing = description
end
end end
end end
end end
@@ -346,14 +340,14 @@ function Database.New(Logger)
end end
-- fill missions -- fill missions
for _, missionZone in pairs(self._tables.MissionZones) do for key, missionZone in pairs(self._tables.MissionZones) do
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
table.insert(stageData.MissionZones, missionZone) table.insert(stageData.MissionZones, missionZone)
end end
end end
-- fill random missions -- fill random missions
for _, missionZone in pairs(self._tables.RandomMissionZones) do for key, missionZone in pairs(self._tables.RandomMissionZones) do
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
table.insert(stageData.RandomMissionZones, missionZone) table.insert(stageData.RandomMissionZones, missionZone)
end end
@@ -475,7 +469,6 @@ function Database.New(Logger)
return self return self
end end
---@type table<string, boolean>
local is_group_taken = {} local is_group_taken = {}
local getAvailableGroups = function() local getAvailableGroups = function()
@@ -628,14 +621,13 @@ function Database:loadBlueSamUnits()
samData.buildingKilos = number samData.buildingKilos = number
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY } local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then
layer_object = layer_object --[[@as TextBox]]
local description = layer_object.text local description = layer_object.text
if description and description ~= "" then if description and description ~= "" then
samData.briefing = description samData.briefing = description
@@ -646,7 +638,7 @@ function Database:loadBlueSamUnits()
end end
end end
end end
else else
MissionEditorWarnings.Add("Buildable number for " .. blueSamZone .. " is invalid") MissionEditorWarnings.Add("Buildable number for " .. blueSamZone .. " is invalid")
end end
end end
@@ -679,13 +671,13 @@ function Database:LoadZoneData(missionZoneName)
for _, groupName in pairs(groups) do for _, groupName in pairs(groups) do
if MizGroupsManager.IsGroupStatic(groupName) == true then if MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName) local object = StaticObject.getByName(groupName)
if object and object:getCoalition() == coalition.side.RED then if object and object:getCoalition() == coalition.side.RED then
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName) table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
elseif object then elseif object then
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName) table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
end end
else else
local group = Group.getByName(groupName) local group = Group.getByName(groupName)
if group and group:getCoalition() == coalition.side.RED then if group and group:getCoalition() == coalition.side.RED then
@@ -737,14 +729,13 @@ function Database:LoadZoneData(missionZoneName)
---Adds Briefings ---Adds Briefings
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY } local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startsWith(layer_object.name, "briefing_", true) then if layer_object.name and Util.startsWith(layer_object.name, "briefing_", true) then
layer_object = layer_object --[[@as TextBox]]
local description = layer_object.text local description = layer_object.text
if description and description ~= "" then if description and description ~= "" then
self._tables.MissionZoneData[missionZoneName].description = description self._tables.MissionZoneData[missionZoneName].description = description
@@ -794,13 +785,12 @@ function Database:loadFarpData()
-- check briefings -- check briefings
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then if string.lower(layer.name) == "author" then
for _, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY } local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
layer_object = layer_object --[[@as TextBox]]
if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text local description = layer_object.text
if description and description ~= "" then if description and description ~= "" then
@@ -815,6 +805,8 @@ function Database:loadFarpData()
else else
MissionEditorWarnings.Add("Buildable number for " .. farpZone .. " is invalid.") MissionEditorWarnings.Add("Buildable number for " .. farpZone .. " is invalid.")
end end
end end
end end
end end
@@ -969,7 +961,6 @@ function Database:getBriefingForStage(stagename)
return stageZone.StageBriefing or "" return stageZone.StageBriefing or ""
end end
---@param stagename string
---@return Array<string> ---@return Array<string>
function Database:getMissionsForStage(stagename) function Database:getMissionsForStage(stagename)
local stageZone = self._tables.StageZones[stagename] local stageZone = self._tables.StageZones[stagename]
@@ -977,7 +968,6 @@ function Database:getMissionsForStage(stagename)
return stageZone.MissionZones return stageZone.MissionZones
end end
---@param stagename string
---@return Array<string> ---@return Array<string>
function Database:getRandomMissionsForStage(stagename) function Database:getRandomMissionsForStage(stagename)
local stageZone = self._tables.StageZones[stagename] local stageZone = self._tables.StageZones[stagename]
@@ -985,14 +975,13 @@ function Database:getRandomMissionsForStage(stagename)
return stageZone.RandomMissionZones return stageZone.RandomMissionZones
end end
---@param missionZoneName string
---@return MissionZoneData? ---@return MissionZoneData?
function Database:getMissionDataForZone(missionZoneName) function Database:getMissionDataForZone(missionZoneName)
return self._tables.MissionZoneData[missionZoneName] return self._tables.MissionZoneData[missionZoneName]
end end
---@param stageName string ---@param stageName string
---@return Array<string> result airbase Names ---@return table result airbase Names
function Database:getAirbaseNamesInStage(stageName) function Database:getAirbaseNamesInStage(stageName)
local stageData = self._tables.StageZones[stageName] local stageData = self._tables.StageZones[stageName]
if not stageData then return {} end if not stageData then return {} end
@@ -1006,7 +995,6 @@ function Database:getFarpNamesInStage(stageName)
return stageData.FarpZones or {} return stageData.FarpZones or {}
end end
---@param farpZoneName string
---@return FarpZoneData? ---@return FarpZoneData?
function Database:getFarpDataForZone(farpZoneName) function Database:getFarpDataForZone(farpZoneName)
local farpData = self._tables.FarpZoneData[farpZoneName] local farpData = self._tables.FarpZoneData[farpZoneName]
@@ -1022,7 +1010,6 @@ function Database:getAirbaseDataForZone(baseName)
return baseData return baseData
end end
---@param stageName string
---@return string? ---@return string?
function Database:getStageBriefingForStage(stageName) function Database:getStageBriefingForStage(stageName)
local stageData = self._tables.StageZones[stageName] local stageData = self._tables.StageZones[stageName]
+43 -68
View File
@@ -32,9 +32,7 @@ do
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?) ---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?)
do -- STAGE NUMBER CHANGED do -- STAGE NUMBER CHANGED
---@type Array<OnStageChangedListener>
local OnStageNumberChangedListeners = {} local OnStageNumberChangedListeners = {}
---@type Array<fun(number:integer, laneIdentifier:string?)>
local OnStageNumberChangedHandlers = {} local OnStageNumberChangedHandlers = {}
---Add a stage zone number changed listener ---Add a stage zone number changed listener
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier) ---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier)
@@ -48,8 +46,7 @@ do
---@class OnStageNumberChangeCompleteListener ---@class OnStageNumberChangeCompleteListener
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?) ---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
---@type Array<OnStageNumberChangeCompleteListener>
local OnStageNumberChangeCompleteListeners = {} local OnStageNumberChangeCompleteListeners = {}
---@param listener OnStageNumberChangeCompleteListener ---@param listener OnStageNumberChangeCompleteListener
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener) SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
@@ -68,7 +65,7 @@ do
end) end)
for _, callable in pairs(OnStageNumberChangedListeners) do for _, callable in pairs(OnStageNumberChangedListeners) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnStageNumberChanged(newStageNumber, laneIdentifier) callable:OnStageNumberChanged(newStageNumber, laneIdentifier)
end) end)
if err then if err then
@@ -77,14 +74,14 @@ do
end end
for _, callable in pairs(OnStageNumberChangedHandlers) do for _, callable in pairs(OnStageNumberChangedHandlers) do
local _, err = pcall(callable, newStageNumber, laneIdentifier) local succ, err = pcall(callable, newStageNumber, laneIdentifier)
if err then if err then
logError(err) logError(err)
end end
end end
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier) callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
end) end)
if err then if err then
@@ -113,12 +110,9 @@ do
table.insert(onWeaponFiredListeners, weaponFiredListener) table.insert(onWeaponFiredListeners, weaponFiredListener)
end end
---@param unit Unit
---@param weapon Weapon
---@param target Unit?
local triggerWeaponFired = function(unit, weapon, target) local triggerWeaponFired = function(unit, weapon, target)
for _, callable in pairs(onWeaponFiredListeners) do for _, callable in pairs(onWeaponFiredListeners) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnWeaponFired(unit, weapon, target) callable:OnWeaponFired(unit, weapon, target)
end) end)
@@ -127,15 +121,12 @@ do
end end
end end
end end
---@class OnLandEventListener
---@field OnUnitLanded fun(self:OnLandEventListener, initiatorUnit:Unit, airbase:Airbase)
---@type table<string,Array<OnLandEventListener>>
local onLandEventListeners = {} local onLandEventListeners = {}
---Add an event listener to a specific unit ---Add an event listener to a specific unit
---@param unitName string to call when the unit lands ---@param unitName string to call when the unit lands
---@param landListener OnLandEventListener table with function OnUnitLanded(self, initiatorUnit, airbase) ---@param landListener table table with function OnUnitLanded(self, initiatorUnit, airbase)
SpearheadEvents.addOnUnitLandEventListener = function(unitName, landListener) SpearheadEvents.addOnUnitLandEventListener = function(unitName, landListener)
if type(landListener) ~= "table" then if type(landListener) ~= "table" then
warn("Event handler not of type table/object") warn("Event handler not of type table/object")
@@ -171,11 +162,6 @@ do
end end
do -- ON RTB do -- ON RTB
---@class OnGroupRTBListener
---@field OnGroupRTB fun(self:OnGroupRTBListener, groupName:string)
---@type table<string,Array<OnGroupRTBListener>>
local OnGroupRTBListeners = {} local OnGroupRTBListeners = {}
---Adds a function to the events listener that triggers when a group publishes themselves RTB. ---Adds a function to the events listener that triggers when a group publishes themselves RTB.
---This is only available when a ROUTE is created via the Spearhead.RouteUtil ---This is only available when a ROUTE is created via the Spearhead.RouteUtil
@@ -200,7 +186,7 @@ do
if groupName ~= nil then if groupName ~= nil then
if OnGroupRTBListeners[groupName] then if OnGroupRTBListeners[groupName] then
for _, callable in pairs(OnGroupRTBListeners[groupName]) do for _, callable in pairs(OnGroupRTBListeners[groupName]) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnGroupRTB(groupName) callable:OnGroupRTB(groupName)
end) end)
if err then if err then
@@ -211,15 +197,11 @@ do
end end
end end
---@class OnGroupRTBInTenListener
---@field OnGroupRTBInTen fun(self:OnGroupRTBInTenListener, groupName:string)
---@type table<string,Array<OnGroupRTBInTenListener>>
local OnGroupRTBInTenListeners = {} local OnGroupRTBInTenListeners = {}
---Adds a function to the events listener that triggers when a group publishes themselves RTB. ---Adds a function to the events listener that triggers when a group publishes themselves RTB.
---This is only available when a ROUTE is created via the Spearhead.RouteUtil ---This is only available when a ROUTE is created via the Spearhead.RouteUtil
---@param groupName string the groupname to expect ---@param groupName string the groupname to expect
---@param handlingObject OnGroupRTBInTenListener object with OnGroupRTBInTen(self, groupName) ---@param handlingObject table object with OnGroupRTBInTen(self, groupName)
SpearheadEvents.addOnGroupRTBInTenListener = function(groupName, handlingObject) SpearheadEvents.addOnGroupRTBInTenListener = function(groupName, handlingObject)
if type(handlingObject) ~= "table" then if type(handlingObject) ~= "table" then
warn("Event handler not of type table/object") warn("Event handler not of type table/object")
@@ -239,7 +221,7 @@ do
if groupName ~= nil then if groupName ~= nil then
if OnGroupRTBInTenListeners[groupName] then if OnGroupRTBInTenListeners[groupName] then
for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnGroupRTBInTen(groupName) callable:OnGroupRTBInTen(groupName)
end) end)
if err then if err then
@@ -252,17 +234,10 @@ do
end end
do -- ON Station do -- ON Station
---@class OnGroupOnStationListener
---@field OnGroupOnStation fun(self:OnGroupOnStationListener, groupName:string)
---@type table<string,Array<OnGroupOnStationListener>>
local OnGroupOnStationListeners = {} local OnGroupOnStationListeners = {}
---Adds a function to the events listener that triggers when a group publishes themselves RTB. ---Adds a function to the events listener that triggers when a group publishes themselves RTB.
---This is only available when a ROUTE is created via the Spearhead.RouteUtil ---This is only available when a ROUTE is created via the Spearhead.RouteUtil
---@param groupName string the groupname to expect ---@param groupName string the groupname to expect
---@param handlingObject OnGroupOnStationListener object with OnGroupOnStation(self, groupName)
SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject) SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject)
if type(handlingObject) ~= "table" then if type(handlingObject) ~= "table" then
warn("Event handler not of type table/object") warn("Event handler not of type table/object")
@@ -282,7 +257,7 @@ do
if groupName ~= nil then if groupName ~= nil then
if OnGroupOnStationListeners[groupName] then if OnGroupOnStationListeners[groupName] then
for _, callable in pairs(OnGroupOnStationListeners[groupName]) do for _, callable in pairs(OnGroupOnStationListeners[groupName]) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnGroupOnStation(groupName) callable:OnGroupOnStation(groupName)
end) end)
if err then if err then
@@ -295,18 +270,15 @@ do
end end
do -- PLAYER ENTER UNIT do -- PLAYER ENTER UNIT
---@class OnPlayerEnterUnitListener
---@field OnPlayerEntersUnit fun(self:OnPlayerEnterUnitListener, unit:Unit)
---@type Array<OnPlayerEnterUnitListener>
local playerEnterUnitListeners = {} local playerEnterUnitListeners = {}
---comment ---comment
---@param listener OnPlayerEnterUnitListener object with OnPlayerEntersUnit(self, unit) ---@param listener table object with OnPlayerEntersUnit(self, unit)
SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener) SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener)
if type(listener) ~= "table" then if type(listener) ~= "table" then
warn("Unit lost Event listener not of type table/object") warn("Unit lost Event listener not of type table/object")
return return
end end
table.insert(playerEnterUnitListeners, listener) table.insert(playerEnterUnitListeners, listener)
end end
@@ -314,7 +286,7 @@ do
if unit ~= nil then if unit ~= nil then
if playerEnterUnitListeners then if playerEnterUnitListeners then
for _, callable in pairs(playerEnterUnitListeners) do for _, callable in pairs(playerEnterUnitListeners) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnPlayerEntersUnit(unit) callable:OnPlayerEntersUnit(unit)
end) end)
if err then if err then
@@ -327,7 +299,7 @@ do
end end
do -- Ejection events do -- Ejection events
local unitEjectListeners = {} local unitEjectListeners = {}
SpearheadEvents.AddOnUnitEjectedListener = function(listener) SpearheadEvents.AddOnUnitEjectedListener = function(listener)
if type(listener) ~= "table" then if type(listener) ~= "table" then
@@ -343,13 +315,13 @@ do
local e = {} local e = {}
function e:onEvent(event) function e:onEvent(event)
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
local unit = event.initiator --[[@as Unit]] local unit = event.initiator
local airbase = event.place --[[@as Airbase]] local airbase = event.place
if unit ~= nil then if unit ~= nil then
local name = unit:getName() local name = unit:getName()
if onLandEventListeners[name] then if onLandEventListeners[name] then
for _, callable in pairs(onLandEventListeners[name]) do for _, callable in pairs(onLandEventListeners[name]) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnUnitLanded(unit, airbase) callable:OnUnitLanded(unit, airbase)
end) end)
if err then if err then
@@ -364,15 +336,15 @@ do
event.id == world.event.S_EVENT_CRASH or event.id == world.event.S_EVENT_CRASH or
event.id == world.event.S_EVENT_EJECTION or event.id == world.event.S_EVENT_EJECTION or
event.id == world.event.S_EVENT_UNIT_LOST then event.id == world.event.S_EVENT_UNIT_LOST then
local object = event.initiator --[[@as Unit]] local object = event.initiator
if object and object.getName then if object and object.getName then
logDebug("Receiving death event from: " .. object:getName()) logDebug("Receiving death event from: " .. object:getName())
end end
if object and object.getName and OnUnitLostListeners[object:getName()] then if object and object.getName and OnUnitLostListeners[object:getName()] then
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
local _, err = pcall(function() local succ, err = pcall(function()
callable:OnUnitLost(object) callable:OnUnitLost(object)
end) end)
@@ -383,11 +355,15 @@ do
end end
end end
if event.id == world.event.S_EVENT_SHOT then if event.id == world.event.S_EVENT_EJECTION then
end
local shooter = event.initiator --[[@as Unit]] if event.id == world.event.S_EVENT_SHOT then
local weapon = event.weapon --[[@as Weapon]]
local target = event.target --[[@as Unit?]] local shooter = event.initiator
local weapon = event.weapon
local target = event.target
triggerWeaponFired(shooter, weapon, target) triggerWeaponFired(shooter, weapon, target)
end end
@@ -396,32 +372,28 @@ do
Persistence.UpdateNow() Persistence.UpdateNow()
end end
---@type table<string, boolean>
local AI_GROUPS = {} local AI_GROUPS = {}
---@param unit Unit local function CheckAndTriggerSpawnAsync(unit, time)
---@param _ any
local function CheckAndTriggerSpawnAsync(unit, _) local function isPlayer(unit)
---@param checkUnit Unit if unit == nil then return false, "unit is nil" end
---@return boolean, string? if unit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
local function isPlayer(checkUnit) if Object.getCategory(unit) ~= Object.Category.UNIT then
if checkUnit == nil then return false, "unit is nil" end
if checkUnit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
if Object.getCategory(checkUnit) ~= Object.Category.UNIT then
return false, "object is not a unit" return false, "object is not a unit"
end end
if checkUnit:isExist() ~= true then return false, "unit does not exist" end if unit:isExist() ~= true then return false, "unit does not exist" end
local group = checkUnit:getGroup() local group = unit:getGroup()
if group ~= nil then if group ~= nil then
if AI_GROUPS[group:getName()] == true then if AI_GROUPS[group:getName()] == true then
return false return false
end end
local players = DcsUtil.getAllPlayerUnits() local players = DcsUtil.getAllPlayerUnits()
local unitName = checkUnit:getName() local unitName = unit:getName()
for _, playerUnit in pairs(players) do for i, unit in pairs(players) do
if playerUnit:getName() == unitName then if unit:getName() == unitName then
return true return true
end end
end end
@@ -431,8 +403,11 @@ do
end end
if isPlayer(unit) == true then if isPlayer(unit) == true then
local groupId = unit:getGroup():getID()
SpearheadEvents.TriggerPlayerEntersUnit(unit) SpearheadEvents.TriggerPlayerEntersUnit(unit)
end end
return nil
end end
if event.id == world.event.S_EVENT_BIRTH then if event.id == world.event.S_EVENT_BIRTH then
+5 -13
View File
@@ -8,7 +8,6 @@ do --setup route util
---@param attackHelos boolean ---@param attackHelos boolean
---@return table ---@return table
local function GetCAPTargetTypes(attackHelos) local function GetCAPTargetTypes(attackHelos)
---@type table<number, string>
local targetTypes = { local targetTypes = {
[1] = "Planes", [1] = "Planes",
} }
@@ -54,9 +53,8 @@ do --setup route util
---comment ---comment
---@param groupName string ---@param groupName string
---@param position Vec3 ---@param position table { x, y}
---@param altitude number ---@param altitude number
---@param deviationdistance number
---@param speed number ---@param speed number
---@param duration number ---@param duration number
---@param engageHelos boolean ---@param engageHelos boolean
@@ -183,7 +181,7 @@ do --setup route util
end end
---comment ---comment
---@param position Vec3 ---@param position table { x, y}
---@param altitude number ---@param altitude number
---@param speed number ---@param speed number
---@param childTasks table ---@param childTasks table
@@ -212,8 +210,7 @@ do --setup route util
---comment ---comment
---@param groupName string groupName you're creating this route for ---@param groupName string groupName you're creating this route for
---@param airdromeId number airdromeId ---@param airdromeId number airdromeId
---@param capPoint Vec3 ---@param capPoint table { x, z }
---@param racetrackSecondPoint Vec3?
---@param altitude number ---@param altitude number
---@param speed number ---@param speed number
---@param durationOnStation number ---@param durationOnStation number
@@ -261,8 +258,7 @@ do --setup route util
if racetrackSecondPoint then orbitType = "Race-Track" end if racetrackSecondPoint then orbitType = "Race-Track" end
local basePoint = base:getPoint() local basePoint = base:getPoint()
---@type Array<table> local points = {}
local points
if racetrackSecondPoint == nil then if racetrackSecondPoint == nil then
points = { points = {
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks), [1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
@@ -308,7 +304,6 @@ do --setup route util
end end
local group = Group.getByName(groupName) local group = Group.getByName(groupName)
---@type Vec3?
local pos; local pos;
local i = 1 local i = 1
if group == nil then if group == nil then
@@ -398,9 +393,6 @@ do --setup route util
}, "" }, ""
end end
---@param pointA Vec3
---@param pointB Vec3
---@return table, string?
ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB) ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB)
return { return {
id = "Mission", id = "Mission",
@@ -469,7 +461,7 @@ do --setup route util
} }
} }
} }
}, nil }, ""
end end
end end
+100 -115
View File
@@ -2,6 +2,7 @@ local Events = require("classes.spearhead_events")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local PersistenceConfig = require("classes.configuration.PersistenceConfig") local PersistenceConfig = require("classes.configuration.PersistenceConfig")
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage") local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage") local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
@@ -34,6 +35,7 @@ local singletonInstance = nil
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@return GlobalStageManager ---@return GlobalStageManager
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager) function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
if singletonInstance ~= nil then if singletonInstance ~= nil then
return singletonInstance return singletonInstance
end end
@@ -42,7 +44,7 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
logger:info("Using Stage Log Level: " .. logLevel) logger:info("Using Stage Log Level: " .. logLevel)
local self = setmetatable({}, GlobalStageManager) local self = setmetatable({}, GlobalStageManager)
singletonInstance = self singletonInstance = self
self.database = database self.database = database
self.stageConfig = stageConfig self.stageConfig = stageConfig
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -50,15 +52,14 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
self.logger = logger self.logger = logger
if stageConfig.isAutoStages ~= true then if stageConfig.isAutoStages ~= true then
logger:warn( logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
"Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
end end
Events.AddStageNumberChangedListener(self) Events.AddStageNumberChangedListener(self)
for _, stageName in pairs(database:getStagezoneNames()) do for _, stageName in pairs(database:getStagezoneNames()) do
logger:debug("Found stage zone with name: " .. stageName) logger:debug("Found stage zone with name: " .. stageName)
local parseResult = self:ParseStageName(stageName) local parseResult = self:ParseStageName(stageName)
if parseResult.isValid == false then if parseResult.isValid == false then
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason) logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason)
@@ -80,12 +81,12 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
stage:AddStageCompleteListener(self) stage:AddStageCompleteListener(self)
self._stageRepository:AddStage(stage) self._stageRepository:AddStage(stage)
elseif parseResult.stageType == "WaitingStage" then elseif parseResult.stageType == "WaitingStage" then
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData, local waitingStage = WaitingStage.New(database, stageConfig, logger, initData, parseResult.waitingStageSeconds, spawnManager)
parseResult.waitingStageSeconds, spawnManager)
waitingStage:AddStageCompleteListener(self) waitingStage:AddStageCompleteListener(self)
self._stageRepository:AddStage(waitingStage) self._stageRepository:AddStage(waitingStage)
end end
end end
end end
singletonInstance = self singletonInstance = self
@@ -102,11 +103,11 @@ function GlobalStageManager:Start()
local stageLanes = self._stageRepository:getAllStageLanes() local stageLanes = self._stageRepository:getAllStageLanes()
for _, stageLane in pairs(stageLanes) do for _, stageLane in pairs(stageLanes) do
local stageLaneIdentifier = stageLane:GetStageLaneIdentifier() local stageLaneIdentifier = stageLane:GetStageLaneIdentifier()
local persistedStage = Persistence.GetActiveStage(stageLaneIdentifier) local persistedStage = Persistence.GetActiveStage(stageLaneIdentifier)
if persistedStage then if persistedStage then
self.logger:info("Loaded persisted stage " .. self.logger:info("Loaded persisted stage " .. persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
stageLane:SetActiveStageIndex(persistedStage) stageLane:SetActiveStageIndex(persistedStage)
Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier) Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier)
else else
@@ -138,16 +139,17 @@ end
---@param stageName string ---@param stageName string
---@return StageNameParseResult ---@return StageNameParseResult
function GlobalStageManager:ParseStageName(stageName) function GlobalStageManager:ParseStageName(stageName)
local split = Util.split_string(stageName, "_") local split = Util.split_string(stageName, "_")
if Util.tableLength(split) < 3 then if Util.tableLength(split) < 3 then
return { isValid = false, invalidReason = "Stage zone with name " .. return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a order number or valid format" }
stageName .. " does not have a order number or valid format" }
end end
local typePart = string.lower(split[1]) local typePart = string.lower(split[1])
if typePart == "missionstage" then if typePart == "missionstage" then
local orderNumberString = string.lower(split[2]) local orderNumberString = string.lower(split[2])
---@type StageType ---@type StageType
local stageType = "PrimaryStage" local stageType = "PrimaryStage"
@@ -159,16 +161,15 @@ function GlobalStageManager:ParseStageName(stageName)
local first = orderNumberString:sub(1, 1) local first = orderNumberString:sub(1, 1)
if tonumber(first) == nil then -- first character is the lane identifier only if it's not a number if tonumber(first) == nil then -- first character is the lane identifier only if it's not a number
stageLaneIdentifier = string.lower(first) stageLaneIdentifier = string.lower(first)
orderNumberString = orderNumberString:sub(2) orderNumberString = orderNumberString:sub(2)
end end
local orderNumber = tonumber(orderNumberString) local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then if orderNumber == nil then
return { isValid = false, invalidReason = "Stage zone with name " .. return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
stageName .. " does not have a valid order number : " .. orderNumberString }
end end
local stageDisplayName = split[3] local stageDisplayName = split[3]
---@type StageNameParseResult ---@type StageNameParseResult
local result = { local result = {
@@ -181,19 +182,18 @@ function GlobalStageManager:ParseStageName(stageName)
} }
return result return result
elseif typePart == "waitingstage" then elseif typePart == "waitingstage" then
local stageType = "WaitingStage" local stageType = "WaitingStage"
local orderNumberString = split[2] local orderNumberString = split[2]
local orderNumber = tonumber(orderNumberString) local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then if orderNumber == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
stageName .. " does not have a valid order number : " .. orderNumberString }
end end
local waitingSecondsString = split[3] local waitingSecondsString = split[3]
local waitingSeconds = tonumber(waitingSecondsString) local waitingSeconds = tonumber(waitingSecondsString)
if waitingSeconds == nil then if waitingSeconds == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
end end
local stageDisplayName = "Waiting Stage " .. orderNumber local stageDisplayName = "Waiting Stage " .. orderNumber
@@ -209,9 +209,9 @@ function GlobalStageManager:ParseStageName(stageName)
waitingStageSeconds = waitingSeconds waitingStageSeconds = waitingSeconds
} }
return result return result
end end
return { isValid = false, invalidReason = "Stage zone with name " .. return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " has an unrecognized type: " .. typePart }
stageName .. " has an unrecognized type: " .. typePart }
end end
---@param stage Stage ---@param stage Stage
@@ -223,8 +223,7 @@ function GlobalStageManager:OnStageComplete(stage)
local stageLane = self._stageRepository:getStageLane(laneIdentifier) local stageLane = self._stageRepository:getStageLane(laneIdentifier)
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then if not stageLane or not stageLane:IsCurrentStageIndexComplete() then
self.logger:debug("Stage lane " .. self.logger:debug("Stage lane " .. (laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
(laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
return return
end end
@@ -235,7 +234,7 @@ function GlobalStageManager:OnStageComplete(stage)
stageLane:SetActiveStageIndex(nextStageIndex) stageLane:SetActiveStageIndex(nextStageIndex)
if stageLane:IsDefaultStageLane() then if stageLane:IsDefaultStageLane() then
-- COMPLETION IN THE DEFAULT LANE -- COMPLETION IN THE DEFAULT LANE
-- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index" -- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index"
local allLanes = self._stageRepository:getAllStageLanes() local allLanes = self._stageRepository:getAllStageLanes()
@@ -243,9 +242,7 @@ function GlobalStageManager:OnStageComplete(stage)
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = lane:GetNextChapterStart() local nextChapterStart = lane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then if nextChapterStart == nextStageIndex then
self.logger:debug("Lane " .. self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is at chapter start for next stage index " .. nextStageIndex)
(lane:GetStageLaneIdentifier() or "default") ..
" is at chapter start for next stage index " .. nextStageIndex)
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier()) Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
lane:SetActiveStageIndex(nextStageIndex) lane:SetActiveStageIndex(nextStageIndex)
end end
@@ -259,6 +256,7 @@ function GlobalStageManager:OnStageComplete(stage)
if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = defaultStageLane:GetNextChapterStart() local nextChapterStart = defaultStageLane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then if nextChapterStart == nextStageIndex then
-- 2. Check if all side lanes are at or beyond the next chapter starts -- 2. Check if all side lanes are at or beyond the next chapter starts
local allLanes = self._stageRepository:getAllStageLanes() local allLanes = self._stageRepository:getAllStageLanes()
local allSideLanesReady = true local allSideLanesReady = true
@@ -267,19 +265,15 @@ function GlobalStageManager:OnStageComplete(stage)
-- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active" -- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active"
if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then
allSideLanesReady = false allSideLanesReady = false
self.logger:debug("Lane " .. self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break break
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again -- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
elseif lane:GetStageLaneState() == "BetweenChapters" then elseif lane:GetStageLaneState() == "BetweenChapters" then
local nextChapter = lane:GetNextChapterStart() local nextChapter = lane:GetNextChapterStart()
if nextChapter and nextChapter < nextStageIndex then if nextChapter and nextChapter < nextStageIndex then
allSideLanesReady = false allSideLanesReady = false
self.logger:debug("Lane " .. self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break break
end end
end end
@@ -287,14 +281,13 @@ function GlobalStageManager:OnStageComplete(stage)
end end
if allSideLanesReady then if allSideLanesReady then
self.logger:debug("All side lanes are ready for next stage index " .. self.logger:debug("All side lanes are ready for next stage index " .. nextStageIndex .. ", activating default lane")
nextStageIndex .. ", activating default lane")
Events.PublishStageNumberChanged(nextStageIndex, nil) Events.PublishStageNumberChanged(nextStageIndex, nil)
defaultStageLane:SetActiveStageIndex(nextStageIndex) defaultStageLane:SetActiveStageIndex(nextStageIndex)
else else
self.logger:debug("Not all side lanes are ready for next stage index " .. self.logger:debug("Not all side lanes are ready for next stage index " .. nextStageIndex .. ", default lane will not be activated")
nextStageIndex .. ", default lane will not be activated")
end end
end end
end end
end end
@@ -315,6 +308,7 @@ end
---@public ---@public
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier) function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now -- only react on "main" lane changes, ignore other lanes for now
if stageLaneIdentifier ~= nil then return end if stageLaneIdentifier ~= nil then return end
@@ -327,13 +321,13 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneId
if stageLaneIdentifier ~= nil then return end if stageLaneIdentifier ~= nil then return end
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber)) self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
---@type Array<Group>
local groups = {} local groups = {}
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
local group = player:getGroup() local group = player:getGroup()
if group then if group then
table.insert(groups, group) groups[group:getID()] = group
end end
end end
@@ -355,98 +349,91 @@ function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
if laneIdentifier == stageLaneIdentifier then if laneIdentifier == stageLaneIdentifier then
if stageNumber >= startStage and stageNumber < stopStage then if stageNumber >= startStage and stageNumber < stopStage then
self.logger:debug("Drawing " .. self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:Draw() drawing:Draw()
else else
self.logger:debug("Drawing " .. self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:Remove() drawing:Remove()
end end
end end
end end
end end
function GlobalStageManager:PrintMermaidStage() function GlobalStageManager:PrintMermaidStage()
local lanes = self._stageRepository:getAllStageLanes() local lanes = self._stageRepository:getAllStageLanes()
---@type table<string, string>
local nodes = {} local nodes = {}
---@type table<string, string>
local edges = {} local edges = {}
---@type table<string, Array<number>>
local stageIndicesByLane = {} -- Track all stage indices per lane local stageIndicesByLane = {} -- Track all stage indices per lane
local mainLaneId = "default" local mainLaneId = "default"
-- Color palette for lanes (26 vibrant colors optimized for dark mode) -- Color palette for lanes (26 vibrant colors optimized for dark mode)
local laneColors = { local laneColors = {
"#FF6B6B", -- 1: Red "#FF6B6B", -- 1: Red
"#4ECDC4", -- 2: Teal "#4ECDC4", -- 2: Teal
"#45B7D1", -- 3: Blue "#45B7D1", -- 3: Blue
"#FFA502", -- 4: Orange "#FFA502", -- 4: Orange
"#95E1D3", -- 5: Mint "#95E1D3", -- 5: Mint
"#F38181", -- 6: Coral "#F38181", -- 6: Coral
"#AA96DA", -- 7: Purple "#AA96DA", -- 7: Purple
"#FCBAD3", -- 8: Pink "#FCBAD3", -- 8: Pink
"#A8E6CF", -- 9: Light green "#A8E6CF", -- 9: Light green
"#FFD3B6", -- 10: Peach "#FFD3B6", -- 10: Peach
"#FFAAA5", -- 11: Light red "#FFAAA5", -- 11: Light red
"#FF8B94", -- 12: Rose "#FF8B94", -- 12: Rose
"#FFEAA7", -- 13: Butter "#FFEAA7", -- 13: Butter
"#DFE6E9", -- 14: Gray "#DFE6E9", -- 14: Gray
"#00B894", -- 15: Emerald "#00B894", -- 15: Emerald
"#0984E3", -- 16: Cobalt "#0984E3", -- 16: Cobalt
"#6C5CE7", -- 17: Indigo "#6C5CE7", -- 17: Indigo
"#A29BFE", -- 18: Lavender "#A29BFE", -- 18: Lavender
"#FD79A8", -- 19: Magenta "#FD79A8", -- 19: Magenta
"#FDCB6E", -- 20: Gold "#FDCB6E", -- 20: Gold
"#6C757D", -- 21: Slate "#6C757D", -- 21: Slate
"#20C997", -- 22: Seafoam "#20C997", -- 22: Seafoam
"#E74C3C", -- 23: Scarlet "#E74C3C", -- 23: Scarlet
"#3498DB", -- 24: Dodger blue "#3498DB", -- 24: Dodger blue
"#9B59B6", -- 25: Amethyst "#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise "#1ABC9C", -- 26: Turquoise
} }
---@type table<string, string>
local laneColorMap = {} -- Map lane ID to color local laneColorMap = {} -- Map lane ID to color
---@type number
local colorIndex = 1 local colorIndex = 1
-- Build nodes and collect all stage indices per lane -- Build nodes and collect all stage indices per lane
for _, lane in ipairs(lanes) do for _, lane in ipairs(lanes) do
local laneId = lane:GetStageLaneIdentifier() or "default" local laneId = lane:GetStageLaneIdentifier() or "default"
local stageIndices = lane:GetAllStageIndices() local stageIndices = lane:GetAllStageIndices()
stageIndicesByLane[laneId] = stageIndices stageIndicesByLane[laneId] = stageIndices
-- Assign color to this lane -- Assign color to this lane
laneColorMap[laneId] = laneColors[colorIndex] laneColorMap[laneId] = laneColors[colorIndex]
colorIndex = colorIndex + 1 colorIndex = colorIndex + 1
if colorIndex > #laneColors then if colorIndex > #laneColors then
colorIndex = 1 colorIndex = 1
end end
-- Create nodes for each stage index in this lane -- Create nodes for each stage index in this lane
for _, stageIndex in ipairs(stageIndices) do for _, stageIndex in ipairs(stageIndices) do
local stages = lane:GetStagesAtIndex(stageIndex) local stages = lane:GetStagesAtIndex(stageIndex)
if stages then if stages then
for _, stage in ipairs(stages) do for _, stage in ipairs(stages) do
local nodeId = laneId .. "_" .. stageIndex local nodeId = laneId .. "_" .. stageIndex
local stageType = stage:GetStageType() local stageType = stage:GetStageType()
local stageName = stage.stageName or stage.zoneName local stageName = stage.stageName or stage.zoneName
-- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes -- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes
---@type string
local bracketLabel local bracketLabel
if laneId == "default" then if laneId == "default" then
bracketLabel = "[" .. stageIndex .. "]" bracketLabel = "[" .. stageIndex .. "]"
else else
bracketLabel = "[" .. laneId .. stageIndex .. "]" bracketLabel = "[" .. laneId .. stageIndex .. "]"
end end
-- Combined label: [bracket] Name -- Combined label: [bracket] Name
local label = bracketLabel .. " " .. stageName local label = bracketLabel .. " " .. stageName
-- Different node shapes for different stage types -- Different node shapes for different stage types
local nodeShape = "[" local nodeShape = "["
local nodeEnd = "]" local nodeEnd = "]"
@@ -457,14 +444,14 @@ function GlobalStageManager:PrintMermaidStage()
nodeShape = "[[" nodeShape = "[["
nodeEnd = "]]" nodeEnd = "]]"
end end
nodes[nodeId] = string.format(' %s%s"%s"%s', nodes[nodeId] = string.format(' %s%s"%s"%s',
nodeId, nodeShape, label, nodeEnd) nodeId, nodeShape, label, nodeEnd)
end end
end end
end end
end end
-- Add edges for sequential stages within the same lane -- Add edges for sequential stages within the same lane
for laneId, stageIndices in pairs(stageIndicesByLane) do for laneId, stageIndices in pairs(stageIndicesByLane) do
for i = 1, #stageIndices - 1 do for i = 1, #stageIndices - 1 do
@@ -472,7 +459,7 @@ function GlobalStageManager:PrintMermaidStage()
local toIdx = stageIndices[i + 1] local toIdx = stageIndices[i + 1]
local fromNodeId = laneId .. "_" .. fromIdx local fromNodeId = laneId .. "_" .. fromIdx
local toNodeId = laneId .. "_" .. toIdx local toNodeId = laneId .. "_" .. toIdx
-- Check if this is a chapter boundary (gap) -- Check if this is a chapter boundary (gap)
local isChapter = false local isChapter = false
for _, lane in ipairs(lanes) do for _, lane in ipairs(lanes) do
@@ -482,12 +469,12 @@ function GlobalStageManager:PrintMermaidStage()
break break
end end
end end
local label = isChapter and "|chapter|" or "" local label = isChapter and "|chapter|" or ""
table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId)) table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId))
end end
end end
-- Get main lane reference -- Get main lane reference
local mainStageLane = nil local mainStageLane = nil
for _, lane in ipairs(lanes) do for _, lane in ipairs(lanes) do
@@ -496,15 +483,15 @@ function GlobalStageManager:PrintMermaidStage()
break break
end end
end end
-- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts -- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts
-- Side lanes activate when main completes the stage BEFORE the chapter start -- Side lanes activate when main completes the stage BEFORE the chapter start
if mainStageLane then if mainStageLane then
for _, lane in ipairs(lanes) do for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier() local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]] local sideIndices = stageIndicesByLane[sideId]
-- For each stage in the side lane -- For each stage in the side lane
for _, stageIdx in ipairs(sideIndices) do for _, stageIdx in ipairs(sideIndices) do
-- Check if this is a chapter start in the side lane -- Check if this is a chapter start in the side lane
@@ -519,40 +506,40 @@ function GlobalStageManager:PrintMermaidStage()
end end
end end
end end
-- Add dependencies from SIDE lanes gating the MAIN lane at chapter boundaries -- Add dependencies from SIDE lanes gating the MAIN lane at chapter boundaries
-- Main cannot advance to the next chapter until ALL side lanes are at or above that chapter index -- Main cannot advance to the next chapter until ALL side lanes are at or above that chapter index
if mainStageLane then if mainStageLane then
local mainIndices = stageIndicesByLane[mainLaneId] local mainIndices = stageIndicesByLane[mainLaneId]
-- For each chapter start in main lane (skip first one) -- For each chapter start in main lane (skip first one)
for i = 2, #mainIndices do for i = 2, #mainIndices do
local nextIdx = mainIndices[i] local nextIdx = mainIndices[i]
-- Check if nextIdx is a chapter start (there's a gap before it) -- Check if nextIdx is a chapter start (there's a gap before it)
if mainStageLane:IsChapterStart(nextIdx) then if mainStageLane:IsChapterStart(nextIdx) then
-- Before main can progress to nextIdx, all side lanes must be at >= nextIdx -- Before main can progress to nextIdx, all side lanes must be at >= nextIdx
for _, lane in ipairs(lanes) do for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier() local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]] local sideIndices = stageIndicesByLane[sideId]
-- Find the appropriate gate node for this side lane -- Find the appropriate gate node for this side lane
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage -- Use first stage >= nextIdx if it exists, otherwise use the highest stage
local gateStageIdx = nil local gateStageIdx = nil
for _, idx in ipairs(sideIndices) do for _, idx in ipairs(sideIndices) do
if idx >= nextIdx then if idx >= nextIdx then
gateStageIdx = idx gateStageIdx = idx
break break
end end
end end
-- If no stage >= nextIdx, use the highest stage in this lane -- If no stage >= nextIdx, use the highest stage in this lane
if gateStageIdx == nil and #sideIndices > 0 then if gateStageIdx == nil and #sideIndices > 0 then
gateStageIdx = sideIndices[#sideIndices] gateStageIdx = sideIndices[#sideIndices]
end end
if gateStageIdx then if gateStageIdx then
local fromNodeId = sideId .. "_" .. gateStageIdx local fromNodeId = sideId .. "_" .. gateStageIdx
local toNodeId = mainLaneId .. "_" .. nextIdx local toNodeId = mainLaneId .. "_" .. nextIdx
@@ -563,26 +550,25 @@ function GlobalStageManager:PrintMermaidStage()
end end
end end
end end
-- Build complete Mermaid diagram -- Build complete Mermaid diagram
local diagramLines = { "graph TD" } local diagramLines = {"graph TD"}
-- Add class definitions for each lane with colors and contrasting text -- Add class definitions for each lane with colors and contrasting text
for laneId, color in pairs(laneColorMap) do for laneId, color in pairs(laneColorMap) do
table.insert(diagramLines, table.insert(diagramLines, string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
end end
-- Add nodes -- Add nodes
for _, nodeStr in pairs(nodes) do for _, nodeStr in pairs(nodes) do
table.insert(diagramLines, nodeStr) table.insert(diagramLines, nodeStr)
end end
-- Add edges -- Add edges
for _, edgeStr in pairs(edges) do for _, edgeStr in pairs(edges) do
table.insert(diagramLines, edgeStr) table.insert(diagramLines, edgeStr)
end end
-- Apply classes to nodes -- Apply classes to nodes
for nodeId in pairs(nodes) do for nodeId in pairs(nodes) do
local laneId = nodeId:match("(.+)_[0-9]+$") local laneId = nodeId:match("(.+)_[0-9]+$")
@@ -590,10 +576,9 @@ function GlobalStageManager:PrintMermaidStage()
table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId)) table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId))
end end
end end
-- Print as single multi-line message -- Print as single multi-line message
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" .. local diagram = "========== STAGE FLOW DIAGRAM ==========\n" .. table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
self.logger:info(diagram) self.logger:info(diagram)
end end
@@ -601,10 +586,10 @@ end
---@param stageNumber number ---@param stageNumber number
---@param stageLaneIdentifier string? nil for default lan ---@param stageLaneIdentifier string? nil for default lan
---@return boolean | nil ---@return boolean | nil
GlobalStageManager.isStageComplete = function(stageNumber, stageLaneIdentifier) GlobalStageManager.isStageComplete = function (stageNumber, stageLaneIdentifier)
if singletonInstance == nil then if singletonInstance == nil then
Logger.new("StageManager", "INFO"):warn( Logger.new("StageManager", "INFO"):warn("GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
"GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
return nil return nil
end end
@@ -41,7 +41,7 @@ end
function SpearheadGroup:SpawnCorpsesOnly() function SpearheadGroup:SpawnCorpsesOnly()
if self._isSpawned == true then return end if self._isSpawned == true then return end
self._spawnManager:SpawnCorpsesOnly(self._groupName) self._spawnManager:SpawnCorpsesOnly(self._groupName)
self._isSpawned = true self._isSpawned = true
@@ -57,7 +57,7 @@ function SpearheadGroup:Spawn(lateStart)
uncontrolled = lateStart, uncontrolled = lateStart,
} }
local _, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent) local spawnedObject, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
self._isStatic = isStatic self._isStatic = isStatic
self._isSpawned = true self._isSpawned = true
end end
@@ -91,13 +91,13 @@ function SpearheadGroup:GetCoalition()
end end
---comment ---comment
---@return Array<Unit|StaticObject> result list of objects ---@return table result list of objects
function SpearheadGroup:GetObjects() function SpearheadGroup:GetObjects()
---@type Array<Unit|StaticObject>
local result = {} local result = {}
if self._isStatic == true then if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName) local staticObject = StaticObject.getByName(self._groupName)
if staticObject then if staticObject then
table.insert(result, staticObject) table.insert(result, staticObject)
end end
else else
@@ -105,7 +105,7 @@ function SpearheadGroup:GetObjects()
if not group then return {} end if not group then return {} end
for _, unit in pairs(group:getUnits()) do for _, unit in pairs(group:getUnits()) do
table.insert(result, unit) table.insert(result, unit)
end end
end end
return result return result
end end
@@ -123,7 +123,7 @@ function SpearheadGroup:GetAsUnits()
if not group then return {} end if not group then return {} end
for _, unit in pairs(group:getUnits()) do for _, unit in pairs(group:getUnits()) do
table.insert(result, unit) table.insert(result, unit)
end end
return result return result
end end
@@ -133,7 +133,7 @@ function SpearheadGroup:GetAllUnitPositions()
local result = {} local result = {}
if self._isStatic == true then if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName) local staticObject = StaticObject.getByName(self._groupName)
if staticObject then if staticObject then
table.insert(result, staticObject:getPoint()) table.insert(result, staticObject:getPoint())
end end
else else
@@ -141,7 +141,7 @@ function SpearheadGroup:GetAllUnitPositions()
if not group then return {} end if not group then return {} end
for _, unit in pairs(group:getUnits()) do for _, unit in pairs(group:getUnits()) do
table.insert(result, unit:getPoint()) table.insert(result, unit:getPoint())
end end
end end
return result return result
end end
@@ -50,10 +50,16 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
---@type table<string, Vec3> ---@type table<string, Vec3>
local redUnitsPos = {} local redUnitsPos = {}
local buildable = false
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
buildable = true
end
for _, groupName in pairs(blueSamData.groups) do for _, groupName in pairs(blueSamData.groups) do
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true) local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
if spearheadGroup then if spearheadGroup then
if spearheadGroup:GetCoalition() == 2 or spearheadGroup:GetCoalition() == 0 then if spearheadGroup:GetCoalition() == 2 or spearheadGroup:GetCoalition() == 0 then
table.insert(self._blueGroups, spearheadGroup) table.insert(self._blueGroups, spearheadGroup)
end end
@@ -72,7 +78,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
--Cleanup units --Cleanup units
local cleanup_distance = 5 local cleanup_distance = 5
for _, blueUnitPos in pairs(blueUnitsPos) do for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos) local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then if distance <= cleanup_distance then
@@ -134,9 +140,7 @@ end
function BlueSam:SpawnGroups() function BlueSam:SpawnGroups()
for unitName, needsCleanup in pairs(self._cleanupUnits) do for unitName, needsCleanup in pairs(self._cleanupUnits) do
if needsCleanup then DcsUtil.DestroyUnit(unitName)
DcsUtil.DestroyUnit(unitName)
end
end end
for _, group in pairs(self._blueGroups) do for _, group in pairs(self._blueGroups) do
@@ -42,11 +42,11 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
self._groups = {} self._groups = {}
self._padNames = {} self._padNames = {}
self._supplyHubs = {} self._supplyHubs = {}
if farpData then if farpData then
self._padNames = farpData.padNames self._padNames = farpData.padNames
for _, supplyHubName in pairs(farpData.supplyHubNames) do for _, supplyHubName in pairs(farpData.supplyHubNames) do
local supplyHub = SupplyHub.new(database, logger, supplyHubName) local supplyHub = SupplyHub.new(database, logger, supplyHubName)
if supplyHub then if supplyHub then
@@ -55,7 +55,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
end end
for _, groupName in pairs(farpData.groups) do for _, groupName in pairs(farpData.groups) do
local group = SpearheadGroup.New(groupName, spawnManager, true) local group = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._groups, group) table.insert(self._groups, group)
group:Destroy() group:Destroy()
@@ -90,7 +90,7 @@ function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
local cleanup_distance = 5 local cleanup_distance = 5
for _, blueUnitPos in pairs(blueUnitsPos) do for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos) local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then if distance <= cleanup_distance then
@@ -113,7 +113,6 @@ end
function StageBase:SpawnRedUnits() function StageBase:SpawnRedUnits()
---comment ---comment
---@param groups Array<SpearheadGroup> ---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups) local spawnAsync = function(groups)
for _, group in pairs(groups) do for _, group in pairs(groups) do
group:Spawn() group:Spawn()
@@ -143,7 +142,6 @@ end
function StageBase:SpawnBlueUnits() function StageBase:SpawnBlueUnits()
---comment ---comment
---@param groups Array<SpearheadGroup> ---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups) local spawnAsync = function(groups)
for _, group in pairs(groups) do for _, group in pairs(groups) do
group:Spawn() group:Spawn()
@@ -174,7 +172,7 @@ function StageBase:ActivateBlueStage()
else else
self:FinaliseBlueStage() self:FinaliseBlueStage()
end end
end end
function StageBase:FinaliseBlueStage() function StageBase:FinaliseBlueStage()
@@ -42,7 +42,7 @@ function SupplyHub.new(database, logger, zoneName)
end end
self._zone = DcsUtil.getZoneByName(zoneName) self._zone = DcsUtil.getZoneByName(zoneName)
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
self._inZone = {} self._inZone = {}
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -37,7 +37,6 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
---@param params UnpackCrateParam ---@param params UnpackCrateParam
---@param time number ---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time) local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2) local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo local alreadySpawned = params.unpackedItems / params.groupsPerKilo
@@ -51,7 +50,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
if params.unpackedKilos >= params.kilos or spawned == false then if params.unpackedKilos >= params.kilos or spawned == false then
return return
end end
return time + 0.5 return time + 0.5
end end
@@ -77,6 +76,8 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
self._buildableMission = nil self._buildableMission = nil
end end
if self._buildableMission == nil then if self._buildableMission == nil then
self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name) self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name)
end end
@@ -98,14 +99,25 @@ function BuildableZone:OnBuildingComplete() end
---@field unpackedItems number ---@field unpackedItems number
---@field unpackedKilos number ---@field unpackedKilos number
---@param _ BuildableMission? ---@param mission BuildableMission?
---@param kilos number ---@param kilos number
function BuildableZone:OnCrateDroppedOff(_, kilos) function BuildableZone:OnCrateDroppedOff(mission, kilos)
self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name) self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name)
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
---@param params UnpackCrateParam ---@param params UnpackCrateParam
---@param time number ---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time) local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2) local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo local alreadySpawned = params.unpackedItems / params.groupsPerKilo
@@ -120,21 +132,10 @@ function BuildableZone:OnCrateDroppedOff(_, kilos)
params.self:FinaliseCrate(params.kilos) params.self:FinaliseCrate(params.kilos)
return return
end end
return time + 2 return time + 2
end end
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2) timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end end
@@ -189,7 +190,7 @@ function BuildableZone:SpawnAmount(amount)
return nil return nil
end end
for _ = 1, amount do for i = 1, amount do
local spawned = spawnOne() local spawned = spawnOne()
if spawned ~= true then if spawned ~= true then
self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name) self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name)
+9 -12
View File
@@ -4,8 +4,8 @@
---@field private _isDefaultStageLane boolean ---@field private _isDefaultStageLane boolean
---@field private _stageLaneIdentifier string ---@field private _stageLaneIdentifier string
---@field private _activeStageIndex number ---@field private _activeStageIndex number
---@field private _stagesInLaneByIndex table<string, Array<Stage>> ---@field private _stagesInLaneByIndex table<number, Array<Stage>>
---@field private _chapterStarts table<string, boolean>? ---@field private _chapterStarts table<number, boolean>?
---@field private _stageLaneState StageLaneState ---@field private _stageLaneState StageLaneState
---@field private _maxStageIndex number ---@field private _maxStageIndex number
local StageLane = {} local StageLane = {}
@@ -16,7 +16,6 @@ StageLane.__index = StageLane
---| "BetweenChapters" ---| "BetweenChapters"
---| "Completed" ---| "Completed"
---@type string?
StageLane.DefaultLaneKey = nil StageLane.DefaultLaneKey = nil
function StageLane.New(laneIdentifier) function StageLane.New(laneIdentifier)
@@ -38,21 +37,21 @@ end
---@param stage Stage ---@param stage Stage
function StageLane:AddStage(stage) function StageLane:AddStage(stage)
local stageIndex = stage:GetStageIndex() local stageIndex = stage:GetStageIndex()
if not self._stagesInLaneByIndex[tostring(stageIndex)] then if not self._stagesInLaneByIndex[stageIndex] then
self._stagesInLaneByIndex[tostring(stageIndex)] = {} self._stagesInLaneByIndex[stageIndex] = {}
end end
if not self._maxStageIndex or stageIndex > self._maxStageIndex then if not self._maxStageIndex or stageIndex > self._maxStageIndex then
self._maxStageIndex = stageIndex self._maxStageIndex = stageIndex
end end
table.insert(self._stagesInLaneByIndex[tostring(stageIndex)], stage) table.insert(self._stagesInLaneByIndex[stageIndex], stage)
end end
---@param stageNumber number ---@param stageNumber number
---@return boolean? ---@return boolean?
function StageLane:IsStageIndexComplete(stageNumber) function StageLane:IsStageIndexComplete(stageNumber)
local stages = self._stagesInLaneByIndex[tostring(stageNumber)] local stages = self._stagesInLaneByIndex[stageNumber]
if not stages then if not stages then
return nil return nil
end end
@@ -89,7 +88,7 @@ end
---@param stageNumber number ---@param stageNumber number
function StageLane:SetActiveStageIndex(stageNumber) function StageLane:SetActiveStageIndex(stageNumber)
if self._stagesInLaneByIndex[tostring(stageNumber)] == nil then if self._stagesInLaneByIndex[stageNumber] == nil then
self._stageLaneState = "BetweenChapters" -- stage number is not in this lane, so we are between chapters self._stageLaneState = "BetweenChapters" -- stage number is not in this lane, so we are between chapters
elseif stageNumber > self._maxStageIndex then elseif stageNumber > self._maxStageIndex then
self._stageLaneState = "Completed" -- stage number is beyond the max stage index self._stageLaneState = "Completed" -- stage number is beyond the max stage index
@@ -109,7 +108,6 @@ end
--- Chapter starts are the first StageIndex after a gap in stage numbers. <br> --- Chapter starts are the first StageIndex after a gap in stage numbers. <br>
--- For example, if the stage numbers are 1, 2, 3, 5, 6, 7, then stage number 5 is a chapter start because there is a gap between 3 and 5. <br> --- For example, if the stage numbers are 1, 2, 3, 5, 6, 7, then stage number 5 is a chapter start because there is a gap between 3 and 5. <br>
---@param stageNumber number ---@param stageNumber number
---@return boolean
function StageLane:IsChapterStart(stageNumber) function StageLane:IsChapterStart(stageNumber)
if self._chapterStarts == nil then if self._chapterStarts == nil then
@@ -125,14 +123,13 @@ end
function StageLane:FillChapterStarts() function StageLane:FillChapterStarts()
local previousIndex = nil local previousIndex = nil
---@type table<string, number>
local stageIndices = {} local stageIndices = {}
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
table.insert(stageIndices, tonumber(stageIndex)) table.insert(stageIndices, tonumber(stageIndex))
end end
table.sort(stageIndices) table.sort(stageIndices)
if self._chapterStarts == nil then self._chapterStarts = {} end self._chapterStarts = self._chapterStarts or {}
for _, stageIndex in ipairs(stageIndices) do for _, stageIndex in ipairs(stageIndices) do
if previousIndex == nil then if previousIndex == nil then
-- First stage is always a chapter start -- First stage is always a chapter start
@@ -161,7 +158,7 @@ end
---@param stageIndex number ---@param stageIndex number
---@return Array<Stage>? ---@return Array<Stage>?
function StageLane:GetStagesAtIndex(stageIndex) function StageLane:GetStagesAtIndex(stageIndex)
return self._stagesInLaneByIndex[tostring(stageIndex)] return self._stagesInLaneByIndex[stageIndex]
end end
---@return number? ---@return number?
@@ -49,6 +49,7 @@ end
function StageRepository:AddStage(stage) function StageRepository:AddStage(stage)
local stageLaneIdentifier = stage:GetStageLaneIdentifier() or StageLane.DefaultLaneKey local stageLaneIdentifier = stage:GetStageLaneIdentifier() or StageLane.DefaultLaneKey
if self.StageLanes[tostring(stageLaneIdentifier)] == nil then if self.StageLanes[tostring(stageLaneIdentifier)] == nil then
self.StageLanes[tostring(stageLaneIdentifier)] = StageLane.New(stageLaneIdentifier) self.StageLanes[tostring(stageLaneIdentifier)] = StageLane.New(stageLaneIdentifier)
end end
@@ -105,7 +105,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
end end
self.stageName = initData.stageDisplayName self.stageName = initData.stageDisplayName
self._stageType = stageType self._stageType = stageType
self.OnPostStageComplete = nil self.OnPostStageComplete = nil
self.OnPostBlueActivated = nil self.OnPostBlueActivated = nil
@@ -160,26 +160,25 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
self._logger:info("Initiating new Stage with name: " .. self.zoneName) self._logger:info("Initiating new Stage with name: " .. self.zoneName)
---comment ---comment
---@param selfA Stage ---@param self Stage
---@param time number? ---@param time number?
---@return number|nil self.CheckContinuousAsync = function (self, time)
self.CheckContinuousAsync = function (selfA, time)
self:CheckAndUpdateSelf()
selfA:CheckAndUpdateSelf() if self:IsComplete() == true then
if selfA:IsComplete() == true then self:NotifyComplete()
selfA:NotifyComplete()
return nil return nil
end end
return time + 20 return time + 20
end end
do -- load tables do -- load tables
local missionZones = database:getMissionsForStage(self.zoneName) local missionZones = database:getMissionsForStage(self.zoneName)
self._logger:debug("Found " .. Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName) self._logger:debug("Found " .. Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName)
for _, missionZone in pairs(missionZones) do for _, missionZone in pairs(missionZones) do
local mission = ZoneMission.new(missionZone, self._missionPriority, database, logger, self, spawnManager) local mission = ZoneMission.new(missionZone, self._missionPriority, database, logger, self, spawnManager)
if mission then if mission then
self._db.missionsByCode[mission.code] = mission self._db.missionsByCode[mission.code] = mission
@@ -234,7 +233,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
table.insert(self._db.missions, mission) table.insert(self._db.missions, mission)
end end
end end
else else
self._logger:info("Using persisted random mission with name: " .. missionName .. " and zone: " .. missionZonePicked) self._logger:info("Using persisted random mission with name: " .. missionName .. " and zone: " .. missionZonePicked)
for _, mission in pairs(missions) do for _, mission in pairs(missions) do
if string.lower(mission.zoneName) == string.lower(missionZonePicked) then if string.lower(mission.zoneName) == string.lower(missionZonePicked) then
@@ -285,14 +284,14 @@ end
function Stage:IsComplete() function Stage:IsComplete()
if self._currentStageState >= StageState.Blue then return true end if self._currentStageState >= StageState.Blue then return true end
for _, mission in pairs(self._db.sams) do for i, mission in pairs(self._db.sams) do
local state = mission:getState() local state = mission:getState()
if state == "ACTIVE" or state == "NEW" or state =="WAITING" then if state == "ACTIVE" or state == "NEW" or state =="WAITING" then
return false return false
end end
end end
for _, mission in pairs(self._db.missions) do for i, mission in pairs(self._db.missions) do
local state = mission:getState() local state = mission:getState()
if state == "ACTIVE" or state == "NEW" then if state == "ACTIVE" or state == "NEW" then
return false return false
@@ -326,9 +325,7 @@ function Stage:GetStageName()
return self.stageName return self.stageName
end end
---@return Array<Mission>
function Stage:GetMissions() function Stage:GetMissions()
---@type Array<Mission>
local missions = {} local missions = {}
for _, mission in pairs(self._db.missions) do for _, mission in pairs(self._db.missions) do
table.insert(missions, mission) table.insert(missions, mission)
@@ -373,9 +370,9 @@ function Stage:CheckAndUpdateSelf()
local availableMissionsCount = Util.tableLength(getAvailableMissions()) local availableMissionsCount = Util.tableLength(getAvailableMissions())
local activeCount = getActiveMissionsCount() local activeCount = getActiveMissionsCount()
if activeCount < max and availableMissionsCount > 0 then if activeCount < max and availableMissionsCount > 0 then
for _ = activeCount+1, max do for i = activeCount+1, max do
if availableMissionsCount == 0 then if availableMissionsCount == 0 then
break i = max+1 --exits this loop
else else
local mission = Util.randomFromList(getAvailableMissions()) --[[@as Mission]] local mission = Util.randomFromList(getAvailableMissions()) --[[@as Mission]]
if mission then if mission then
@@ -429,15 +426,15 @@ function Stage:PreActivate()
end end
self._currentStageState = StageState.PreActivated self._currentStageState = StageState.PreActivated
for _, mission in pairs(self._db.sams) do for key, mission in pairs(self._db.sams) do
if mission then if mission then
mission:SpawnInactive() mission:SpawnInactive()
end end
end end
for _, mission in pairs(self._db.missions) do for key, mission in pairs(self._db.missions) do
if mission and mission.missionType == "DEEPSTRIKE" then if mission and mission.missionType == "DEEPSTRIKE" then
mission:SpawnInactive() --Not Really "inactive" mission:SpawnActive()
end end
end end
@@ -482,7 +479,7 @@ function Stage:MarkStage()
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE) drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
drawing.style = "no line" drawing.style = "no line"
end end
return drawing return drawing
end) end)
self._customDrawing:Draw() self._customDrawing:Draw()
@@ -594,12 +591,12 @@ function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
if needsBlueActivation() == true then if needsBlueActivation() == true then
self:ActivateBlueStage() self:ActivateBlueStage()
end end
end end
---@param self Stage ---@param self Stage
---@param _ Mission ---@param mission Mission
Stage.OnMissionComplete = function(self, _) Stage.OnMissionComplete = function(self, mission)
self:CheckAndUpdateSelf() self:CheckAndUpdateSelf()
end end
@@ -654,7 +651,7 @@ function Stage:GetStageStats()
end end
end end
for _, _ in pairs(self._db.sams) do for _, mission in pairs(self._db.sams) do
dead = dead + 1 dead = dead + 1
end end
@@ -682,14 +679,13 @@ function Stage:ActivateBlueStage()
miscGroup:Spawn() miscGroup:Spawn()
end end
---@param selfA Stage ---@param self Stage
---@return number|nil local ActivateBlueAsync = function(self)
local ActivateBlueAsync = function(selfA)
pcall(function() pcall(function()
selfA:MarkStage() self:MarkStage()
end) end)
selfA:ActivateBlueGroups() self:ActivateBlueGroups()
return nil return nil
end end
@@ -1,7 +1,7 @@
local Stage = require("classes.stageClasses.Stages.BaseStage.Stage") local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
local GlobalCapManager = require("classes.capClasses.GlobalCapManager") local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
local StageState = require("classes.stageClasses.Stages.BaseStage.StageState") local StageState = require("classes.stageClasses.Stages.StageState")
---@class ExtraStage : Stage ---@class ExtraStage : Stage
local ExtraStage = {} local ExtraStage = {}
@@ -23,9 +23,10 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager) self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager)
self.OnPostBlueActivated = function (selfStage) self.OnPostBlueActivated = function (selfStage)
selfStage:MarkStage() selfStage:MarkStage()
end end
self.OnPostStageComplete = function (selfStage) self.OnPostStageComplete = function (selfStage)
selfStage:ActivateBlueStage() selfStage:ActivateBlueStage()
end end
@@ -47,6 +48,7 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
return return
end end
local previousActive = self._activeStage
self._activeStage = number self._activeStage = number
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
@@ -60,7 +62,7 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
self:ActivateStage() self:ActivateStage()
end end
if self._currentStageState == StageState.Blue then if self._currentStageState == StageState.BLUE then
self:ActivateBlueStage() self:ActivateBlueStage()
end end
@@ -25,7 +25,7 @@ function WaitingStage.New(database, stageConfig, logger, initData, waitingSecond
self._startTime = nil self._startTime = nil
self.CheckContinuousAsync = function (selfA, time) self.CheckContinuousAsync = function (selfA, time)
if selfA:IsComplete() == true then if selfA:IsComplete() == true then
selfA:NotifyComplete() selfA:NotifyComplete()
return nil return nil
@@ -47,7 +47,7 @@ function WaitingStage:ActivateStage()
timer.scheduleFunction(self.CheckContinuousAsync, self, self._startTime + self._waitTimeSeconds) timer.scheduleFunction(self.CheckContinuousAsync, self, self._startTime + self._waitTimeSeconds)
end end
function WaitingStage:IsComplete() function WaitingStage:IsComplete()
if timer.getTime() > (self._startTime + self._waitTimeSeconds) then return true end if timer.getTime() > (self._startTime + self._waitTimeSeconds) then return true end
return false return false
end end
@@ -56,12 +56,12 @@ function WaitingStage:OnStageNumberChanged()
self._logger:debug("Waiting Stage OnStageNumberChanged override") self._logger:debug("Waiting Stage OnStageNumberChanged override")
end end
function WaitingStage:MarkStage(_) function WaitingStage:MarkStage(stageColor)
self._logger:debug("Waiting Stage MarkStage override") self._logger:debug("Waiting Stage MarkStage override")
end end
function WaitingStage:GetExpectedTime() function WaitingStage:GetExpectedTime()
return self._startTime + self._waitTimeSeconds return self._startTime + self._waitTimeSeconds
end end
return WaitingStage return WaitingStage
@@ -1,5 +1,7 @@
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
local Util = require("classes.util.Util")
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local drawingLogger = Logger.new("CustomDrawing") local drawingLogger = Logger.new("CustomDrawing")
@@ -1,3 +1,4 @@
local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local GlobalConfig = require("classes.configuration.GlobalConfig") local GlobalConfig = require("classes.configuration.GlobalConfig")
@@ -54,7 +55,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
if lineThickness == nil or lineThickness <= 0 then if lineThickness == nil or lineThickness <= 0 then
lineStyle = 0 lineStyle = 0
end end
---@type string
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. "," local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in pairs(points) do for _, point in pairs(points) do
@@ -77,7 +78,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
end end
trigger.action.setMarkupColor(drawID, lineColor) trigger.action.setMarkupColor(drawID, lineColor)
trigger.action.setMarkupTypeLine(drawID, lineStyle) trigger.action.setMarkupTypeLine(drawID, lineStyle)
end end
---@private ---@private
@@ -125,14 +126,14 @@ function DrawingHelper.DrawPolygon(object)
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString) local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
local color = DrawingHelper.ColorToColorTable(free.colorString) local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style) local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
---@type Array<number>
local keys = {} local keys = {}
for k, _ in pairs(free.points) do for k, _ in pairs(free.points) do
table.insert(keys, k) table.insert(keys, k)
end end
table.sort(keys, function(a, b) return a < b end) table.sort(keys, function(a, b) return a < b end)
---@type Array<Vec3>
local points = {} local points = {}
for _, k in ipairs(keys) do for _, k in ipairs(keys) do
local point = free.points[k] local point = free.points[k]
@@ -200,7 +201,7 @@ function DrawingHelper.DrawLine(object)
---@type Array<Vec3> ---@type Array<Vec3>
local points = {} local points = {}
local ids = {} local ids = {}
for _, point in ipairs(object.points) do for _, point in ipairs(object.points) do
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } ) table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } )
@@ -264,7 +265,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'") logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
return "0x00000000" return "0x00000000"
end end
local r = string.format("%02X", math.floor(rgba[1] * 255)) local r = string.format("%02X", math.floor(rgba[1] * 255))
local g = string.format("%02X", math.floor(rgba[2] * 255)) local g = string.format("%02X", math.floor(rgba[2] * 255))
local b = string.format("%02X", math.floor(rgba[3] * 255)) local b = string.format("%02X", math.floor(rgba[3] * 255))
@@ -274,7 +275,6 @@ function DrawingHelper.ColorTableToColorString(rgba)
end end
---@param lineStyle string ---@param lineStyle string
---@return integer
function DrawingHelper.ToLineStyleInteger(lineStyle) function DrawingHelper.ToLineStyleInteger(lineStyle)
lineStyle = lineStyle:lower() lineStyle = lineStyle:lower()
if lineStyle == "no line" then if lineStyle == "no line" then
@@ -1,5 +1,6 @@
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
---@class BattleManager ---@class BattleManager
---@field private _name string ---@field private _name string
@@ -12,6 +13,8 @@ local Util = require("classes.util.Util")
local BattleManager = {} local BattleManager = {}
BattleManager.__index = BattleManager BattleManager.__index = BattleManager
local debugDrawing = false
---@param redGroups Array<SpearheadGroup> ---@param redGroups Array<SpearheadGroup>
---@param blueGroups Array<SpearheadGroup> ---@param blueGroups Array<SpearheadGroup>
---@param name string ---@param name string
@@ -27,8 +30,8 @@ function BattleManager.New(redGroups, blueGroups, name, logLevel)
self._redGroups = redGroups self._redGroups = redGroups
self._blueGroups = blueGroups self._blueGroups = blueGroups
self._logger:debug("BattleManager created with name: " .. self._name self._logger:debug("BattleManager created with name: " .. self._name
.. ", red groups: " .. #self._redGroups .. ", red groups: " .. #self._redGroups
.. ", blue groups: " .. #self._blueGroups) .. ", blue groups: " .. #self._blueGroups)
return self return self
@@ -36,7 +39,6 @@ end
---@param self BattleManager ---@param self BattleManager
---@param time number ---@param time number
---@return number?
local function CheckTask(self, time) local function CheckTask(self, time)
local interval = self:Update() local interval = self:Update()
if not interval then return end if not interval then return end
@@ -88,6 +90,8 @@ function BattleManager:Update()
self._logger:debug("BattleManager Update called for " .. self._name) self._logger:debug("BattleManager Update called for " .. self._name)
local shootChance = 1 -- Adjust this value to control the shooting probability (0.0 to 1.0)
self:LetUnitsShoot(self._redGroups, self._blueGroups) self:LetUnitsShoot(self._redGroups, self._blueGroups)
self:LetUnitsShoot(self._blueGroups, self._redGroups) self:LetUnitsShoot(self._blueGroups, self._redGroups)
@@ -132,7 +136,7 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
expendQtyEnabled = true expendQtyEnabled = true
} }
} }
local controller = unit:getController() local controller = unit:getController()
if controller then if controller then
controller:setTask(shootTask) controller:setTask(shootTask)
@@ -142,14 +146,14 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
end end
end end
end end
end end
---@param unit Unit ---@param unit Unit
---@return number ---@return number
---@return number ---@return number
function BattleManager:getBestAmmo(unit) function BattleManager:getBestAmmo(unit)
local ammo = unit:getAmmo() --[[@as table<number, table>]] local ammo = unit:getAmmo()
if not ammo then return 3221225470, 1 end -- Default ammo if no ammo is found if not ammo then return 3221225470, 1 end -- Default ammo if no ammo is found
@@ -163,9 +167,9 @@ function BattleManager:getBestAmmo(unit)
end end
end end
local entry = Util.randomFromList(shells) --[[@as table]] local entry = Util.randomFromList(shells)
if entry and entry.desc and entry.desc.warhead then if entry and entry.desc and entry.desc.warhead then
local caliber = entry.desc.warhead.caliber --[[@as number]] local caliber = entry.desc.warhead.caliber
if caliber > 50 then if caliber > 50 then
return 258503344128, 1 return 258503344128, 1
else else
@@ -192,7 +196,7 @@ function BattleManager:IsUnitApplicable(unit)
end end
return true return true
end end
---@private ---@private
@@ -210,7 +214,6 @@ function BattleManager:ToShootingHulls(groups)
end end
local hulls = Util.getSeparatedConvexHulls(points, 50) local hulls = Util.getSeparatedConvexHulls(points, 50)
---@type Array<Array<Vec2>>
local enlargedHulls = {} local enlargedHulls = {}
for _, hull in pairs(hulls) do for _, hull in pairs(hulls) do
local enlarged = Util.enlargeConvexHull(hull, 25) local enlarged = Util.enlargeConvexHull(hull, 25)
@@ -233,7 +236,7 @@ end
---@param groupHulls Array<Array<Vec2>> ---@param groupHulls Array<Array<Vec2>>
---@return Vec2? ---@return Vec2?
function BattleManager:GetRandomPoint(origin, groupHulls) function BattleManager:GetRandomPoint(origin, groupHulls)
local hull = Util.randomFromList(groupHulls) --[[@as Array<Vec2>]] local hull = Util.randomFromList(groupHulls) --[[@as Array<Vec2>]]
if not hull then return nil end if not hull then return nil end
local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin) local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin)
@@ -7,7 +7,7 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel
local StageConfig = require("classes.configuration.StageConfig") local StageConfig = require("classes.configuration.StageConfig")
---@class MissionCommandsHelper : OnPlayerEnterUnitListener ---@class MissionCommandsHelper
---@field missionsByCode table<string, Mission> @table of missions by their code ---@field missionsByCode table<string, Mission> @table of missions by their code
---@field enabledByCode table<string, boolean> @table of enabled missions by their code ---@field enabledByCode table<string, boolean> @table of enabled missions by their code
---@field updateNeeded boolean @flag to indicate if an update is needed ---@field updateNeeded boolean @flag to indicate if an update is needed
@@ -31,6 +31,8 @@ local function sortMissions(list, groupPos)
end) end)
end end
local id = 0
local instance = nil local instance = nil
---@return MissionCommandsHelper ---@return MissionCommandsHelper
@@ -55,16 +57,16 @@ function MissionCommandsHelper.getOrCreate()
instance._supplyUnitsTracker:AddOnSupplyUnitEventListener( instance._supplyUnitsTracker:AddOnSupplyUnitEventListener(
{ {
enteredSupplyHub = function(_, unit) enteredSupplyHub = function(self, unit)
if unit == nil then return end if unit == nil then return end
instance.updateNeeded = true instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID()) instance:updateCommandsForGroup(unit:getGroup():getID())
end, end,
exitedSupplyHub = function(_, unit) exitedSupplyHub = function(self, unit)
instance.updateNeeded = true instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID()) instance:updateCommandsForGroup(unit:getGroup():getID())
end, end,
supplyUnitSpawned = function(_, unit) supplyUnitSpawned = function(self, unit)
instance.updateNeeded = true instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID()) instance:updateCommandsForGroup(unit:getGroup():getID())
end end
@@ -369,7 +371,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
do --- secondary missions do --- secondary missions
local count = 0 local count = 0
local path = { [1] = folderNames.secondary } local path = { [1] = folderNames.secondary }
---@type Array<Mission>
local secondaryMissions = {} local secondaryMissions = {}
for code, enabled in pairs(self.enabledByCode) do for code, enabled in pairs(self.enabledByCode) do
if enabled == true then if enabled == true then
@@ -507,6 +509,7 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
local unloadCargoCommand = function(params) local unloadCargoCommand = function(params)
local unitID = params.unitID local unitID = params.unitID
local crateType = params.crateType local crateType = params.crateType
local supplyUnitsTracker = params.supplyUnitsTracker
params.supplyUnitsTracker:UnloadRequested(unitID, crateType, params.commandHelper) params.supplyUnitsTracker:UnloadRequested(unitID, crateType, params.commandHelper)
end end
@@ -515,7 +518,7 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
for cargoType, amount in pairs(cargo) do for cargoType, amount in pairs(cargo) do
local cargoConfig = SupplyConfigHelper.getSupplyConfig(cargoType) local cargoConfig = SupplyConfigHelper.getSupplyConfig(cargoType)
if cargoConfig then if cargoConfig then
for _ = 1, amount do for i = 1, amount do
local path = { [1] = folderNames.cargo } local path = { [1] = folderNames.cargo }
---@type UnloadCargoCommandParams ---@type UnloadCargoCommandParams
local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self
@@ -83,8 +83,7 @@ function SupplyConfigHelper.fromObjectName(name)
return nil return nil
end end
---@param type CrateType ---@param type CrateType
---@return SupplyConfig?
function SupplyConfigHelper.getSupplyConfig(type) function SupplyConfigHelper.getSupplyConfig(type)
return SupplyConfig[type] return SupplyConfig[type]
end end
@@ -29,7 +29,7 @@ local SupplyLoadConfig = {
{ centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, { centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
{ centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, { centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
} }
}, },
["UH-1H"] = { ["UH-1H"] = {
maxInternalLoad = 2000, maxInternalLoad = 2000,
dropZones = { dropZones = {
@@ -10,7 +10,7 @@ local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig"
---@field enteredSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil ---@field enteredSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
---@field exitedSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil ---@field exitedSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
---@class SupplyUnitsTracker : OnPlayerEnterUnitListener ---@class SupplyUnitsTracker
---@field private _supplyUnitsByName table<string, Unit> ---@field private _supplyUnitsByName table<string, Unit>
---@field private _cargoInUnits table<string, table<CrateType, number>> ---@field private _cargoInUnits table<string, table<CrateType, number>>
---@field private _logger Logger ---@field private _logger Logger
@@ -43,8 +43,6 @@ function SupplyUnitsTracker.getOrCreate()
SpearheadEvents.AddOnPlayerEnterUnitListener(singleton) SpearheadEvents.AddOnPlayerEnterUnitListener(singleton)
---@param selfA SupplyUnitsTracker ---@param selfA SupplyUnitsTracker
---@param time number
---@return number?
local function updateTask(selfA, time) local function updateTask(selfA, time)
selfA:Update() selfA:Update()
@@ -136,7 +134,6 @@ end
---@private ---@private
---@param unit Unit ---@param unit Unit
---@return boolean
function SupplyUnitsTracker:IsSupplyUnit(unit) function SupplyUnitsTracker:IsSupplyUnit(unit)
if unit == nil then return false end if unit == nil then return false end
@@ -161,17 +158,15 @@ function SupplyUnitsTracker:AddCargoToUnit(unitID, crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID) local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil then return end if unit == nil then return end
local unitIdStr = tostring(unitID) if self._cargoInUnits[unitID] == nil then
self._cargoInUnits[unitID] = {}
if self._cargoInUnits[unitIdStr] == nil then
self._cargoInUnits[unitIdStr] = {}
end end
if self._cargoInUnits[unitIdStr][crateType] == nil then if self._cargoInUnits[unitID][crateType] == nil then
self._cargoInUnits[unitIdStr][crateType] = 0 self._cargoInUnits[unitID][crateType] = 0
end end
self._cargoInUnits[unitIdStr][crateType] = self._cargoInUnits[unitIdStr][crateType] + 1 self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1
end end
@@ -190,7 +185,7 @@ function SupplyUnitsTracker:RemoveCargoFromUnit(unitID, crateType)
self._cargoInUnits[unitIDStr][crateType] = self._cargoInUnits[unitIDStr][crateType] - 1 self._cargoInUnits[unitIDStr][crateType] = self._cargoInUnits[unitIDStr][crateType] - 1
local hasCargo = false local hasCargo = false
for _, count in pairs(self._cargoInUnits[unitIDStr]) do for type, count in pairs(self._cargoInUnits[unitIDStr]) do
if count > 0 then if count > 0 then
hasCargo = true hasCargo = true
break break
@@ -221,11 +216,13 @@ end
function SupplyUnitsTracker:CheckUnitsInZones() function SupplyUnitsTracker:CheckUnitsInZones()
for _, unit in pairs(self._supplyUnitsByName) do for name, unit in pairs(self._supplyUnitsByName) do
if unit ~= nil and unit:isExist() == true then if unit ~= nil and unit:isExist() == true then
self._logger:debug("Checking unit: " .. unit:getName()) self._logger:debug("Checking unit: " .. unit:getName())
local pos = unit:getPoint() local pos = unit:getPoint()
local group = unit:getGroup()
for hub, enabled in pairs(self._registeredHubs) do for hub, enabled in pairs(self._registeredHubs) do
if enabled == true then if enabled == true then
local zone = hub:GetZone() local zone = hub:GetZone()
@@ -241,7 +238,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end) end)
end end
end end
else else
if self._unitInSupplyHub[tostring(unit:getID())] == true then if self._unitInSupplyHub[tostring(unit:getID())] == true then
self._unitInSupplyHub[tostring(unit:getID())] = false self._unitInSupplyHub[tostring(unit:getID())] = false
@@ -258,7 +255,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end end
end end
self._unitPositions[tostring(unit:getID())] = pos self._unitPositions[unit:getID()] = pos
end end
end end
end end
@@ -295,11 +292,11 @@ local cargoCount = 0
---@param crateType CrateType ---@param crateType CrateType
---@param missionCommandsHelper MissionCommandsHelper ---@param missionCommandsHelper MissionCommandsHelper
function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHelper) function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHelper)
self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType) self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID) local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil or unit:isExist() == false then if unit == nil or unit:isExist() == false then
self._logger:warn("Unload requested for non-existent unit: " .. unitID) self._logger:warn("Unload requested for non-existent unit: " .. unitID)
return return
end end
@@ -308,7 +305,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self._logger:warn("Unload requested for unit with no group: " .. unit:getName()) self._logger:warn("Unload requested for unit with no group: " .. unit:getName())
return return
end end
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType) local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
if cargoConfig == nil then if cargoConfig == nil then
@@ -328,7 +325,6 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self:UpdateWeightForUnit(unit) self:UpdateWeightForUnit(unit)
cargoCount = cargoCount + 1 cargoCount = cargoCount + 1
---@type {name: string, type: string, x: number, y: number}
local cargoSpawnObject = { local cargoSpawnObject = {
name = crateType .. "_" .. cargoCount, name = crateType .. "_" .. cargoCount,
type = cargoConfig.staticType, type = cargoConfig.staticType,
@@ -366,11 +362,11 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
end end
local unit = group:getUnit(1) local unit = group:getUnit(1)
if unit == nil then return end if unit == nil then return end
if unit:isExist() == false then return end if unit:isExist() == false then return end
if unit:inAir() == true then if unit:inAir() == true then
trigger.action.outTextForUnit(unit:getID(), "Land first before crates can be loaded", 10) trigger.action.outTextForUnit(unit:getID(), "Land first before crates can be loaded", 10)
return return
@@ -387,7 +383,7 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
---@param params LoadCargoParams ---@param params LoadCargoParams
local LoadCrateTask = function(params) local LoadCrateTask = function(params)
local loaded = params.self:TryLoadCrateInUnit(params.unit, params.crateType, params.commandHelper) local loaded = params.self:TryLoadCrateInUnit(params.unit, params.crateType, params.commandHelper)
if loaded ~= false then if loaded ~= false then
trigger.action.outTextForUnit(unit:getID(), "Loaded crate :" .. params.crateType, 10) trigger.action.outTextForUnit(unit:getID(), "Loaded crate :" .. params.crateType, 10)
@@ -414,7 +410,7 @@ end
---@param commandHelper MissionCommandsHelper ---@param commandHelper MissionCommandsHelper
---@return boolean ---@return boolean
function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper) function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
local crateConfigA = SupplyConfigHelper.getSupplyConfig(crateType) local crateConfigA = SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfigA == nil then if crateConfigA == nil then
trigger.action.outTextForUnit(unit:getID(), "Invalid crate type: " .. crateType, 5) trigger.action.outTextForUnit(unit:getID(), "Invalid crate type: " .. crateType, 5)
@@ -448,7 +444,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
if group == nil then return false end if group == nil then return false end
local groupID = group:getID() local groupID = group:getID()
commandHelper:updateCommandsForGroup(groupID) commandHelper:updateCommandsForGroup(groupID)
return true return true
end end
@@ -466,7 +462,7 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
return return
end end
end end
end end
@@ -488,11 +484,11 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
if desc == nil or desc.box == nil then if desc == nil or desc.box == nil then
return nil return nil
end end
local objPos = foundObject:getPoint() local objPos = foundObject:getPoint()
local box = desc.box --[[@type table]] local box = desc.box
local heading = 0 local heading = 0
-- Try to get object heading from position vector's forward direction -- Try to get object heading from position vector's forward direction
-- This works for units and other objects that support getPosition -- This works for units and other objects that support getPosition
pcall(function() pcall(function()
@@ -501,13 +497,13 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
heading = math.atan2(objPosition.x.z, objPosition.x.x) heading = math.atan2(objPosition.x.z, objPosition.x.x)
end end
end) end)
-- For rotated objects, we need to rotate the bounding box -- For rotated objects, we need to rotate the bounding box
local minX = box.min.x --[[@as number]] local minX = box.min.x
local maxX = box.max.x --[[@as number]] local maxX = box.max.x
local minZ = box.min.z --[[@as number]] local minZ = box.min.z
local maxZ = box.max.z --[[@as number]] local maxZ = box.max.z
-- If object has significant rotation, apply rotation to bbox corners -- If object has significant rotation, apply rotation to bbox corners
if math.abs(heading) > 0.1 then if math.abs(heading) > 0.1 then
-- Get all 4 corners of bbox in local space -- Get all 4 corners of bbox in local space
@@ -517,21 +513,21 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
{maxX, minZ}, {maxX, minZ},
{maxX, maxZ} {maxX, maxZ}
} }
-- Rotate corners and find new min/max -- Rotate corners and find new min/max
minX, maxX = math.huge, -math.huge minX, maxX = math.huge, -math.huge
minZ, maxZ = math.huge, -math.huge minZ, maxZ = math.huge, -math.huge
for _, corner in ipairs(corners) do for _, corner in ipairs(corners) do
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading) --[[@as number]] local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading)
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading) --[[@as number]] local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading)
minX = math.min(minX, rotX) minX = math.min(minX, rotX)
maxX = math.max(maxX, rotX) maxX = math.max(maxX, rotX)
minZ = math.min(minZ, rotZ) minZ = math.min(minZ, rotZ)
maxZ = math.max(maxZ, rotZ) maxZ = math.max(maxZ, rotZ)
end end
end end
-- Convert relative bbox to world space by adding object position -- Convert relative bbox to world space by adding object position
---@type SupplyUnitsBoundingBox ---@type SupplyUnitsBoundingBox
return { return {
@@ -555,7 +551,7 @@ end
---@param safetyMargin number Safety margin around objects ---@param safetyMargin number Safety margin around objects
---@return boolean True if collision detected ---@return boolean True if collision detected
function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin) function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
-- Apply safety margin to object bbox -- Apply safety margin to object bbox
local objMin = { local objMin = {
x = objBBox.min.x - safetyMargin, x = objBBox.min.x - safetyMargin,
@@ -567,7 +563,7 @@ function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
y = objBBox.max.y + safetyMargin, y = objBBox.max.y + safetyMargin,
z = objBBox.max.z + safetyMargin z = objBBox.max.z + safetyMargin
} }
-- AABB collision detection -- AABB collision detection
return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and
crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and
@@ -581,7 +577,7 @@ end
function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName) function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
local unitPos = unit:getPosition() local unitPos = unit:getPosition()
-- Get unit's heading from the forward vector (x component) -- Get unit's heading from the forward vector (x component)
-- Heading is calculated as: atan2(forward.z, forward.x) -- Heading is calculated as: atan2(forward.z, forward.x)
local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x) local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x)
@@ -592,14 +588,14 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
self._logger:error("Could not get bbox for crate type: " .. crateTypeName) self._logger:error("Could not get bbox for crate type: " .. crateTypeName)
return nil return nil
end end
local crateRelativeBBox = crateDesc.box --[[@as table]] local crateRelativeBBox = crateDesc.box
-- Get drop zone config for this unit -- Get drop zone config for this unit
local dropZones = { local dropZones = {
{ centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 } { centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }
} }
if SupplyLoadConfig[unit:getTypeName()] ~= nil then if SupplyLoadConfig[unit:getTypeName()] ~= nil then
dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones
end end
@@ -616,12 +612,9 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
radius = 100 -- Search a large area radius = 100 -- Search a large area
} }
} }
---@type Array<{ pos: { x: number, y: number, z: number }, bbox: { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } } }>
local occupiedObjects = {}
---@param foundItem Object local occupiedObjects = {}
---@param _ any local found = function(foundItem, val)
local found = function(foundItem, _)
local bbox = self:GetBoundingBoxes(foundItem) local bbox = self:GetBoundingBoxes(foundItem)
if bbox then if bbox then
self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")") self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")")
@@ -655,21 +648,20 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
for distance = zone.minRadius, zone.maxRadius, zone.spacing do for distance = zone.minRadius, zone.maxRadius, zone.spacing do
-- Check multiple positions within the angular slice -- Check multiple positions within the angular slice
local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections
for angle = minAngle, maxAngle, angleStep do for angle = minAngle, maxAngle, angleStep do
local radians = math.rad(angle) local radians = math.rad(angle)
-- Calculate position at this angle and distance, relative to unit's heading -- Calculate position at this angle and distance, relative to unit's heading
-- Angle 0 = forward, 90 = right, 180 = rear, 270 = left -- Angle 0 = forward, 90 = right, 180 = rear, 270 = left
-- Apply unit heading to make angles relative to unit orientation -- Apply unit heading to make angles relative to unit orientation
local worldAngle = radians + unitHeading local worldAngle = radians + unitHeading
local candidateX = unitPos.p.x + distance * math.sin(worldAngle) local candidateX = unitPos.p.x + distance * math.sin(worldAngle)
local candidateZ = unitPos.p.z + distance * math.cos(worldAngle) local candidateZ = unitPos.p.z + distance * math.cos(worldAngle)
local candidateY = land.getHeight({ x = candidateX, y = candidateZ }) local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
-- Convert crate's relative bbox to world space at this position -- Convert crate's relative bbox to world space at this position
---@type { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } }
local crateBBoxWorldSpace = { local crateBBoxWorldSpace = {
min = { min = {
x = candidateX + crateRelativeBBox.min.x, x = candidateX + crateRelativeBBox.min.x,
@@ -25,9 +25,7 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
local BuildableMission = {} local BuildableMission = {}
BuildableMission.__index = BuildableMission BuildableMission.__index = BuildableMission
---@param siteType string
---@param coords string
---@return string
local function getDefaultBriefing(siteType, coords) local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." .. return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." .. "\nYou will need to drop off supplies so they can start building." ..
@@ -45,13 +43,12 @@ end
---@param noLandingZone SpearheadTriggerZone? ---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger ---@param logger Logger
---@param briefing string? ---@param briefing string?
---@return BuildableMission
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing) function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
setmetatable(BuildableMission, Mission) setmetatable(BuildableMission, Mission)
local self = setmetatable({}, { __index = BuildableMission }) local self = setmetatable({}, { __index = BuildableMission })
self._targetZone = targetZone self._targetZone = targetZone
self._database = database self._database = database
self._requiredKilos = requiredKilos self._requiredKilos = requiredKilos
@@ -78,7 +75,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
end end
self.code = tostring(database:GetNewMissionCode()) self.code = tostring(database:GetNewMissionCode())
local splitTargetZoneName = Util.split_string(targetZone.name, "_") local splitTargetZoneName = Util.split_string(targetZone.name, "_")
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
self.name = splitTargetZoneName[3] self.name = splitTargetZoneName[3]
@@ -86,6 +83,13 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self.name = "Resupply" self.name = "Resupply"
end end
local type = "site"
if requiredCrateType == "SAM_CRATE" then
type = "SAM site"
elseif requiredCrateType == "FARP_CRATE" then
type = "FARP"
end
self.zoneName = targetZone.name .. "_supply" self.zoneName = targetZone.name .. "_supply"
self._logger = logger self._logger = logger
self._onCrateDroppedOfListeners = {} self._onCrateDroppedOfListeners = {}
@@ -120,7 +124,6 @@ function BuildableMission:ShowBriefing(groupID)
local unitType = DcsUtil.getUnitTypeFromGroup(group) local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType) local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
local siteType = "FARP" local siteType = "FARP"
if self._crateType == "SAM_CRATE" then if self._crateType == "SAM_CRATE" then
@@ -136,7 +139,7 @@ function BuildableMission:ShowBriefing(groupID)
briefingPart = getDefaultBriefing(siteType, coords) briefingPart = getDefaultBriefing(siteType, coords)
end end
local briefing = "Mission [" .. self.code .. "] " .. self.name .. local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" .. "\n \n" ..
briefingPart .. briefingPart ..
"\n\n" .. "\n\n" ..
@@ -147,20 +150,17 @@ function BuildableMission:ShowBriefing(groupID)
trigger.action.outTextForGroup(groupID, briefing, GlobalConfig:getBriefingTime()) trigger.action.outTextForGroup(groupID, briefing, GlobalConfig:getBriefingTime())
end end
---@param groupID number
function BuildableMission:MarkMissionAreaToGroup(groupID) function BuildableMission:MarkMissionAreaToGroup(groupID)
local groupIdStr = tostring(groupID) if self._markIDsPerGroup[groupID] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupID])
if self._markIDsPerGroup[groupIdStr] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupIdStr])
end end
---@type string
local text = "[" .. self.code .. "] " .. self.name .. " | " .. self._crateType local text = "[" .. self.code .. "] " .. self.name .. " | " .. self._crateType
local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y } local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y }
local markID = DcsUtil.AddMarkToGroup(groupID, text, location) local markID = DcsUtil.AddMarkToGroup(groupID, text, location)
self._markIDsPerGroup[groupIdStr] = markID self._markIDsPerGroup[groupID] = markID
end end
---@private ---@private
@@ -201,10 +201,9 @@ function BuildableMission:SpawnActive()
local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0}) local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0})
self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6) self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6)
self._dropOffZoneDrawing:Draw() self._dropOffZoneDrawing:Draw()
---@param selfA BuildableMission ---@param selfA BuildableMission
---@param time number ---@param time number
---@return number?
local checkForCrateTasks = function (selfA, time) local checkForCrateTasks = function (selfA, time)
selfA:CheckCratesInZone() selfA:CheckCratesInZone()
@@ -267,8 +266,8 @@ function BuildableMission:CheckCratesInZone()
end end
end end
end end
for _, foundCrate in pairs(foundCrates) do for _, foundCrate in pairs(foundCrates) do
local crateConfig = SupplyConfigHelper.fromObjectName(foundCrate:getName()) local crateConfig = SupplyConfigHelper.fromObjectName(foundCrate:getName())
if crateConfig then if crateConfig then
self._droppedKilos = self._droppedKilos + crateConfig.weight self._droppedKilos = self._droppedKilos + crateConfig.weight
@@ -283,7 +282,7 @@ function BuildableMission:CheckCratesInZone()
self:NotifyMissionComplete() self:NotifyMissionComplete()
self._state = "COMPLETED" self._state = "COMPLETED"
end end
if self._state == "COMPLETED" then if self._state == "COMPLETED" then
for groupID, markID in pairs(self._markIDsPerGroup) do for groupID, markID in pairs(self._markIDsPerGroup) do
if markID then if markID then
@@ -24,7 +24,6 @@ local RunwayStrikeMission = {}
---@param runway Runway ---@param runway Runway
---@param database Database ---@param database Database
---@param logger Logger ---@param logger Logger
---@param airbaseName string
---@param runwayBombingTracker RunwayBombingTracker ---@param runwayBombingTracker RunwayBombingTracker
---@return RunwayStrikeMission? ---@return RunwayStrikeMission?
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker) function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
@@ -43,7 +42,7 @@ function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBo
self._runwayZone = self:RunwayToSpearheadZone(runway) self._runwayZone = self:RunwayToSpearheadZone(runway)
self._repairInProgress = false self._repairInProgress = false
self._minKilosForDamage = 100 self._minKilosForDamage = 100
local sections = self:ToSections(runway, 5) local sections = self:ToSections(runway, 5)
--[[ --[[
+-----------+-----------+-----------+-----------+-----------+ +-----------+-----------+-----------+-----------+-----------+
@@ -85,6 +84,7 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
self._logger:debug("Runway hit: " .. self._airportName .. ":" .. self._runway.Name) self._logger:debug("Runway hit: " .. self._airportName .. ":" .. self._runway.Name)
for _, section in pairs(self._runwaySections) do for _, section in pairs(self._runwaySections) do
local zone = self:SectionToSpearheadZone(section) local zone = self:SectionToSpearheadZone(section)
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
if section.kilosHit == nil then if section.kilosHit == nil then
@@ -96,7 +96,7 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
end end
---@param selfA RunwayStrikeMission ---@param selfA RunwayStrikeMission
local updateState = function(selfA, _) local updateState = function(selfA, time)
selfA:UpdateState() selfA:UpdateState()
end end
@@ -154,7 +154,7 @@ function RunwayStrikeMission:Draw()
if runwaySection.drawID == nil then if runwaySection.drawID == nil then
local zone = self:SectionToSpearheadZone(runwaySection) local zone = self:SectionToSpearheadZone(runwaySection)
---@type Free ---@type Free
local drawObject = { local drawObject = {
primitiveType = "Polygon", primitiveType = "Polygon",
@@ -206,18 +206,17 @@ function RunwayStrikeMission:StartRepair()
self._repairInProgress = true self._repairInProgress = true
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name) self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
---comment ---comment
---@param selfA RunwayStrikeMission ---@param selfA any
---@param time number ---@return unknown
---@return number?
local repairTask = function (selfA, time) local repairTask = function (selfA, time)
local interval = selfA:DoRepairCycle() local interval = selfA:DoRepairCycle()
if interval == nil then return nil end if interval == nil then return nil end
return time + interval return time + interval
end end
timer.scheduleFunction(repairTask, self, timer.getTime() + 5) timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
end end
---@return number?
function RunwayStrikeMission:DoRepairCycle() function RunwayStrikeMission:DoRepairCycle()
local interval = 5 local interval = 5
@@ -250,14 +249,12 @@ function RunwayStrikeMission:DoRepairCycle()
end end
---@class StaticSpawn : table ---@class StaticSpawn
---@field category string ---@field category string
---@field type string ---@field type string
---@field y number ---@field y number
---@field x number ---@field x number
---@field heading number ---@field heading number
---@field name string?
---@field hidden boolean?
local counter = 1 local counter = 1
@@ -446,7 +443,6 @@ end
---@private ---@private
---@param runway Runway ---@param runway Runway
---@param numSections number
---@return Array<RunwaySection> ---@return Array<RunwaySection>
function RunwayStrikeMission:ToSections(runway, numSections) function RunwayStrikeMission:ToSections(runway, numSections)
@@ -209,7 +209,7 @@ end
---@return boolean ---@return boolean
function ZoneMission:AllDependenciesMet() function ZoneMission:AllDependenciesMet()
local allDependenciesMet = true local allDependenciesMet = true
for missionName, _ in pairs(self._dependencies) do for missionName, value in pairs(self._dependencies) do
if self._parentStage:IsMissionComplete(missionName) == false then if self._parentStage:IsMissionComplete(missionName) == false then
allDependenciesMet = false allDependenciesMet = false
self._dependencies[missionName] = false self._dependencies[missionName] = false
@@ -227,9 +227,10 @@ end
---@internal ---@internal
---@param checkHealth boolean ---@param checkHealth boolean
---@param _ boolean ---@param messageIfDone boolean
function ZoneMission:UpdateState(checkHealth, _) function ZoneMission:UpdateState(checkHealth, messageIfDone)
if checkHealth == nil then checkHealth = false end if checkHealth == nil then checkHealth = false end
if messageIfDone == false then messageIfDone = true end
if checkHealth == true then if checkHealth == true then
@@ -237,7 +238,7 @@ function ZoneMission:UpdateState(checkHealth, _)
local staticObject = StaticObject.getByName(unitName) local staticObject = StaticObject.getByName(unitName)
if staticObject then if staticObject then
if staticObject:isExist() == true then if staticObject:isExist() == true then
local life0 = staticObject:getDesc().life --[[@as number]] local life0 = staticObject:getDesc().life
if staticObject:getLife() / life0 < 0.3 then if staticObject:getLife() / life0 < 0.3 then
self._logger:debug("exploding unit") self._logger:debug("exploding unit")
trigger.action.explosion(staticObject:getPoint(), 100) trigger.action.explosion(staticObject:getPoint(), 100)
@@ -283,9 +284,7 @@ function ZoneMission:UpdateState(checkHealth, _)
end end
if self._missionGroups.hasTargets == true then if self._missionGroups.hasTargets == true then
---@type number
local total = 0 local total = 0
---@type number
local alive = 0 local alive = 0
for _, units in pairs(self._missionGroups.targetsAlive) do for _, units in pairs(self._missionGroups.targetsAlive) do
@@ -298,13 +297,13 @@ function ZoneMission:UpdateState(checkHealth, _)
end end
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
total = total + 1 --[[@as number]] total = total + 1
if sceneryObject:IsAlive() == true then if sceneryObject:IsAlive() == true then
alive = alive + 1 alive = alive + 1
end end
end end
local deadRatio = (total - alive) / total --[[@as number]] local deadRatio = (total - alive) / total
if deadRatio >= self._completeAtIndex then if deadRatio >= self._completeAtIndex then
self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex) self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex)
self._state = "COMPLETED" self._state = "COMPLETED"
@@ -500,7 +499,6 @@ function ZoneMission:OnUnitLost(object)
local category = Object.getCategory(object) local category = Object.getCategory(object)
if category == Object.Category.UNIT then if category == Object.Category.UNIT then
object = object --[[@as Unit]]
local unitName = object:getName() local unitName = object:getName()
self._logger:debug("UnitName:" .. unitName) self._logger:debug("UnitName:" .. unitName)
@@ -511,7 +509,6 @@ function ZoneMission:OnUnitLost(object)
self._missionGroups.targetsAlive[groupName][unitName] = false self._missionGroups.targetsAlive[groupName][unitName] = false
end end
elseif category == Object.Category.STATIC then elseif category == Object.Category.STATIC then
object = object --[[@as StaticObject]]
local name = object:getName() local name = object:getName()
self._missionGroups.unitsAlive[name][name] = false self._missionGroups.unitsAlive[name][name] = false
@@ -528,12 +525,12 @@ end
---@param unit Object ---@param unit Object
function ZoneMission:MarkLastContact(unit) function ZoneMission:MarkLastContact(unit)
if not unit then if not unit then
self._logger:error("MarkLastContact called with nil unit") self._logger:error("MarkLastContact called with nil unit")
return return
end end
local point = unit:getPoint() local point = unit:getPoint()
if not point then if not point then
self._logger:error("MarkLastContact called with unit without point") self._logger:error("MarkLastContact called with unit without point")
return return
@@ -51,7 +51,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.location = database:GetLocationForMissionZone(zoneName) self.location = database:GetLocationForMissionZone(zoneName)
self.missionTypeDisplay = self.missionType self.missionTypeDisplay = self.missionType
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
return true, "success" return true, "success"
@@ -73,10 +73,9 @@ function Mission:SpawnPersistedState() end
function Mission:SpawnActive() end function Mission:SpawnActive() end
---comment ---comment
---@param _checkHealth boolean ---@param checkHealth boolean
---@param _messageIfDone boolean ---@param messageIfDone boolean
---@diagnostic disable-next-line: unused-local function Mission:UpdateState(checkHealth, messageIfDone) end
function Mission:UpdateState(_checkHealth, _messageIfDone) end
function Mission:StartCheckingContinuous() end function Mission:StartCheckingContinuous() end
function Mission:PercentageComplete() function Mission:PercentageComplete()
@@ -91,7 +90,6 @@ function Mission:ShowBriefing(groupId)
local unitType = DcsUtil.getUnitTypeFromGroup(group) local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType) local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
self._logger:debug("Coords converted: " .. coords) self._logger:debug("Coords converted: " .. coords)
local stateString = self:ToStateString() local stateString = self:ToStateString()
@@ -127,7 +125,7 @@ function Mission:NotifyMissionComplete()
end) end)
end end
local _, _ = pcall(function() local success, err = pcall(function()
SpearheadAPI.Internal.notifyMissionComplete(self.zoneName) SpearheadAPI.Internal.notifyMissionComplete(self.zoneName)
end) end)
@@ -138,9 +136,7 @@ function Mission:ForceMissionComplete()
self:NotifyMissionComplete() self:NotifyMissionComplete()
end end
---@param _groupId number function Mission:MarkMissionAreaToGroup(groupId) end
---@diagnostic disable-next-line: unused-local
function Mission:MarkMissionAreaToGroup(_groupId) end
---endregion ---endregion
@@ -152,27 +148,30 @@ function Mission:ToStateString() return "status: in progress" end
--endregion --endregion
--- @alias MissionPriority do --aliases
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionType --- @alias MissionPriority
--- | "nil" --- | "none"
--- | "STRIKE" --- | "primary"
--- | "DEEPSTRIKE" --- | "secondary"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
---
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "DEEPSTRIKE"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
end
return Mission return Mission
+44 -79
View File
@@ -32,10 +32,6 @@ do -- INIT DCS_UTIL
---| "Cilinder" ---| "Cilinder"
---| "Polygon" ---| "Polygon"
---@class KeyValuePair
---@field key string
---@field value any
---@class SpearheadTriggerZone ---@class SpearheadTriggerZone
---@field name string ---@field name string
---@field location Vec2 ---@field location Vec2
@@ -44,7 +40,7 @@ do -- INIT DCS_UTIL
---@field zone_type SpearheadTriggerZoneType ---@field zone_type SpearheadTriggerZoneType
---@field properties Array<KeyValuePair>? ---@field properties Array<KeyValuePair>?
---@type table<string, SpearheadTriggerZone> ---@type Array<SpearheadTriggerZone>
DCS_UTIL.__trigger_zones = {} DCS_UTIL.__trigger_zones = {}
end end
@@ -70,21 +66,18 @@ do -- INIT DCS_UTIL
STATIC = 5 --CUSTOM CATEGORY STATIC = 5 --CUSTOM CATEGORY
} }
---@type table<string, string>
DCS_UTIL.__airbaseNamesById = {} DCS_UTIL.__airbaseNamesById = {}
---@type table<string, SpearheadTriggerZone> ---@type table<string, SpearheadTriggerZone>
DCS_UTIL.__airbaseZonesByName = {} DCS_UTIL.__airbaseZonesByName = {}
---@type table<string, CoalitionSide>
DCS_UTIL.__airportsStartingCoalition = {} DCS_UTIL.__airportsStartingCoalition = {}
---@type table<string, CoalitionSide>
DCS_UTIL.__warehouseStartingCoalition = {} DCS_UTIL.__warehouseStartingCoalition = {}
function DCS_UTIL.__INIT() function DCS_UTIL.__INIT()
do -- INITS ALL TABLES WITH DATA THAT's from the MIZ environment do -- INITS ALL TABLES WITH DATA THAT's from the MIZ environment
do --init trigger zones do --init trigger zones
for _, trigger_zone in pairs(env.mission.triggers.zones) do for i, trigger_zone in pairs(env.mission.triggers.zones) do
-- reorder verts as they are not ordered correctly in the ME -- reorder verts as they are not ordered correctly in the ME
local verts = {} local verts = {}
if Util.tableLength(trigger_zone.verticies) >= 4 then if Util.tableLength(trigger_zone.verticies) >= 4 then
@@ -111,8 +104,8 @@ do -- INIT DCS_UTIL
if trigger_zone.properties then if trigger_zone.properties then
for _, kvPair in pairs(trigger_zone.properties) do for _, kvPair in pairs(trigger_zone.properties) do
local key = kvPair.key local key = kvPair["key"]
local value = kvPair.value local value = kvPair["value"]
zone.properties[#zone.properties + 1] = { key = key, value = value } zone.properties[#zone.properties + 1] = { key = key, value = value }
end end
end end
@@ -123,7 +116,6 @@ do -- INIT DCS_UTIL
do -- init airports and warehouses do -- init airports and warehouses
if env.warehouses.airports then if env.warehouses.airports then
env.warehouses.airports = env.warehouses.airports --[[@as table<string, any>]]
for warehouse_id, value in pairs(env.warehouses.airports) do for warehouse_id, value in pairs(env.warehouses.airports) do
if warehouse_id ~= nil then if warehouse_id ~= nil then
warehouse_id = tostring(warehouse_id) or "nil" warehouse_id = tostring(warehouse_id) or "nil"
@@ -134,12 +126,12 @@ do -- INIT DCS_UTIL
end end
if env.warehouses.warehouses then if env.warehouses.warehouses then
env.warehouses.warehouses = env.warehouses.warehouses --[[@as table<number, any>]] DCS_UTIL.__warehouseStartingCoalition[-1] = "placeholder"
for warehouse_id, value in pairs(env.warehouses.warehouses) do for warehouse_id, value in pairs(env.warehouses.warehouses) do
if warehouse_id ~= nil then if warehouse_id ~= nil then
local warehouse_id_str = tostring(warehouse_id) or "nil" warehouse_id = tostring(warehouse_id) or "nil"
local coalitionNumber = DCS_UTIL.stringToCoalition(value.coalition) local coalitionNumber = DCS_UTIL.stringToCoalition(value.coalition)
DCS_UTIL.__warehouseStartingCoalition[warehouse_id_str] = coalitionNumber DCS_UTIL.__warehouseStartingCoalition[warehouse_id] = coalitionNumber
end end
end end
end end
@@ -200,7 +192,7 @@ do -- INIT DCS_UTIL
BLUE = 2 BLUE = 2
} }
]] -- ]] --
input = string.lower(input) local input = string.lower(input)
if input == 'neutrals' or input == "neutral" or input == "0" then if input == 'neutrals' or input == "neutral" or input == "0" then
return DCS_UTIL.Coalition.NEUTRAL return DCS_UTIL.Coalition.NEUTRAL
end end
@@ -226,13 +218,11 @@ do -- INIT DCS_UTIL
end end
end end
--- takes a list of units and returns all the units that are in any of the zones --- takes a list of units and returns all the units that are in any of the zones
---@param unit_names Array<string> unit names ---@param unit_names table unit names
---@param zone_names Array<string> zone names ---@param zone_names table zone names
---@return table<number, { unit : Unit|StaticObject, zone_name: string }> ---@return table unit list of objects { unit = UNIT, zone_name = zoneName}
function DCS_UTIL.getUnitsInZones(unit_names, zone_names) function DCS_UTIL.getUnitsInZones(unit_names, zone_names)
---@type Array<Unit|StaticObject>
local units = {} local units = {}
---@type Array<SpearheadTriggerZone> ---@type Array<SpearheadTriggerZone>
@@ -245,25 +235,20 @@ do -- INIT DCS_UTIL
end end
end end
for _, zone_name in pairs(zone_names) do for index, zone_name in pairs(zone_names) do
local zone = DCS_UTIL.__trigger_zones[zone_name] local zone = DCS_UTIL.__trigger_zones[zone_name]
if zone then if zone then
zones[#zones + 1] = zone zones[#zones + 1] = zone
end end
end end
---@type table<number, { unit : Unit|StaticObject, zone_name: string }>
local in_zone_units = {} local in_zone_units = {}
for units_ind = 1, #units do for units_ind = 1, #units do
local lUnit = units[units_ind] local lUnit = units[units_ind]
local isActive = true
local lCat = Object.getCategory(lUnit)
if lCat == Object.Category.UNIT then
local unit = lUnit --[[@as Unit]]
isActive = unit:isActive() == true
end
local unit_pos = lUnit:getPosition().p local unit_pos = lUnit:getPosition().p
for _, zone in pairs(zones) do local lCat = Object.getCategory(lUnit)
if unit_pos and isActive == true then -- it is a unit and is active or it is not a unit for zone_name, zone in pairs(zones) do
if unit_pos and ((lCat == 1 and lUnit:isActive() == true) or lCat ~= 1) then -- it is a unit and is active or it is not a unit
local isInZone = Util.is3dPointInZone(unit_pos, zone) local isInZone = Util.is3dPointInZone(unit_pos, zone)
if isInZone == true then if isInZone == true then
in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name } in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name }
@@ -277,9 +262,9 @@ do -- INIT DCS_UTIL
--- takes a list of groups and returns all the group leaders that are in any of the zones --- takes a list of groups and returns all the group leaders that are in any of the zones
---@param group_names table unit names ---@param group_names table unit names
---@param zone_name string zone names ---@param zone_name string zone names
---@return Array<string> groupnames list of group names ---@return table groupnames list of group names
function DCS_UTIL.getGroupsInZone(group_names, zone_name) function DCS_UTIL.getGroupsInZone(group_names, zone_name)
local zone = DCS_UTIL.__trigger_zones[zone_name] --[[@as SpearheadTriggerZone?]] local zone = DCS_UTIL.__trigger_zones[zone_name]
if zone == nil then if zone == nil then
return {} return {}
end end
@@ -292,13 +277,11 @@ do -- INIT DCS_UTIL
---@param zone SpearheadTriggerZone ---@param zone SpearheadTriggerZone
---@return Array<string> groupnames list of groups that are in the zone ---@return Array<string> groupnames list of groups that are in the zone
function DCS_UTIL.areGroupsInCustomZone(group_names, zone) function DCS_UTIL.areGroupsInCustomZone(group_names, zone)
---@type Array<{ unit : Unit|StaticObject, groupname: string }>
local units = {} local units = {}
if Util.tableLength(group_names) < 1 then return {} end if Util.tableLength(group_names) < 1 then return {} end
for k = 1, #group_names do for k = 1, #group_names do
---@type { unit : Unit|StaticObject|nil, groupname: string }|nil local entry = nil
local entry
local group = Group.getByName(group_names[k]) local group = Group.getByName(group_names[k])
if group ~= nil then if group ~= nil then
entry = { unit = group:getUnit(1), groupname = group_names[k] } entry = { unit = group:getUnit(1), groupname = group_names[k] }
@@ -307,7 +290,7 @@ do -- INIT DCS_UTIL
end end
if entry and entry.unit and entry.unit:isExist() == true then if entry and entry.unit and entry.unit:isExist() == true then
units[#units + 1] = { groupname = entry.groupname, unit = entry.unit } units[#units + 1] = entry
end end
end end
@@ -325,20 +308,20 @@ do -- INIT DCS_UTIL
--- takes a x, y poistion and checks if it is inside any of the zones --- takes a x, y poistion and checks if it is inside any of the zones
---@param x number North South position ---@param x number North South position
---@param z number West East position ---@param z number West East position
---@param zone_names Array<string> zone names ---@param zone_names table zone names
---@return Array<string> zones list of objects { zone_name = zoneName} ---@return table zones list of objects { zone_name = zoneName}
function DCS_UTIL.isPositionInZones(x, z, zone_names) function DCS_UTIL.isPositionInZones(x, z, zone_names)
---@type Array<SpearheadTriggerZone> ---@type Array<SpearheadTriggerZone>
local zones = {} local zones = {}
for _, zone_name in pairs(zone_names) do for index, zone_name in pairs(zone_names) do
local zone = DCS_UTIL.__trigger_zones[zone_name] local zone = DCS_UTIL.__trigger_zones[zone_name]
if zone then if zone then
zones[#zones + 1] = zone zones[#zones + 1] = zone
end end
end end
---@type Array<string>
local result_zones = {} local result_zones = {}
for _, zone in pairs(zones) do for zone_name, zone in pairs(zones) do
if Util.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then if Util.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then
result_zones[#result_zones + 1] = zone.name result_zones[#result_zones + 1] = zone.name
end end
@@ -413,18 +396,6 @@ do -- INIT DCS_UTIL
return nil; return nil;
end end
---@return Array<string>
function DCS_UTIL.getAllGroupCategoryNames()
return {
"airplane",
"helicopter",
"ground",
"ship",
"train",
"static"
}
end
---@type table<string, CoordType> ---@type table<string, CoordType>
local config = local config =
{ {
@@ -437,14 +408,13 @@ do -- INIT DCS_UTIL
---@param location Vec2 ---@param location Vec2
---@param unitType string? ---@param unitType string
---@return string?
function DCS_UTIL.convertVec2ToUnitUsableType(location, unitType) function DCS_UTIL.convertVec2ToUnitUsableType(location, unitType)
local height = land.getHeight(location) local height = land.getHeight(location)
local vec3 = { x = location.x, y = height, z = location.y } local vec3 = { x = location.x, y = height, z = location.y }
unitType = string.lower(unitType or "") local unitType = string.lower(unitType or "")
local conversionType = config[unitType] local conversionType = config[unitType]
if not conversionType then conversionType = "DDM" end if not conversionType then conversionType = "DDM" end
@@ -459,7 +429,6 @@ do -- INIT DCS_UTIL
---@private ---@private
---@param location Vec3 ---@param location Vec3
---@param coordType CoordType ---@param coordType CoordType
---@return string?
function DCS_UTIL.convertToDisplayCoord(location, coordType) function DCS_UTIL.convertToDisplayCoord(location, coordType)
local lattitude, longitude, altitude = coord.LOtoLL(location) local lattitude, longitude, altitude = coord.LOtoLL(location)
@@ -471,7 +440,7 @@ do -- INIT DCS_UTIL
-- Convert DD to DDM (Degrees Decimal Minutes) -- Convert DD to DDM (Degrees Decimal Minutes)
local function dd_to_ddm(dd) local function dd_to_ddm(dd)
local degrees = math.floor(math.abs(dd)) local degrees = math.floor(math.abs(dd))
local minutes = (math.abs(dd) - degrees) * 60 --[[@as number]] local minutes = (math.abs(dd) - degrees) * 60
local sign = dd >= 0 and 1 or -1 local sign = dd >= 0 and 1 or -1
return degrees * sign, minutes return degrees * sign, minutes
end end
@@ -502,7 +471,7 @@ do -- INIT DCS_UTIL
lon_hemisphere, math.abs(lon_deg), lon_min_display, lon_sec_display, lon_hemisphere, math.abs(lon_deg), lon_min_display, lon_sec_display,
altitude * 3,28084) altitude * 3,28084)
end end
end end
---@param zone SpearheadTriggerZone ---@param zone SpearheadTriggerZone
@@ -590,7 +559,6 @@ do -- INIT DCS_UTIL
end end
---@param group Group ---@param group Group
---@return string? unit type name
function DCS_UTIL.getUnitTypeFromGroup(group) function DCS_UTIL.getUnitTypeFromGroup(group)
for _, unit in pairs(group:getUnits()) do for _, unit in pairs(group:getUnits()) do
if unit and unit:isExist() then if unit and unit:isExist() then
@@ -603,11 +571,10 @@ do -- INIT DCS_UTIL
---comment Get all units that are players ---comment Get all units that are players
---@return Array<Unit> units ---@return Array<Unit> units
function DCS_UTIL.getAllPlayerUnits() function DCS_UTIL.getAllPlayerUnits()
---@type Array<Unit>
local units = {} local units = {}
for i = 0, 2 do for i = 0, 2 do
local players = coalition.getPlayers(i) local players = coalition.getPlayers(i)
for _, unit in pairs(players) do for key, unit in pairs(players) do
units[#units + 1] = unit units[#units + 1] = unit
end end
end end
@@ -649,9 +616,9 @@ do -- INIT DCS_UTIL
return result return result
end end
---@param unitName string
function DCS_UTIL.CleanCorpse(unitName) function DCS_UTIL.CleanCorpse(unitName)
unitName = "dead_" .. unitName local unitName = "dead_" .. unitName
local object = StaticObject.getByName(unitName) local object = StaticObject.getByName(unitName)
if object then if object then
@@ -665,11 +632,11 @@ do -- INIT DCS_UTIL
---@field b number ---@field b number
---@field a number ---@field a number
local __drawID = 4210 local drawID = 4210
function DCS_UTIL.GetNextDrawID() function DCS_UTIL.GetNextDrawID()
__drawID = __drawID + 1 drawID = drawID + 1
return __drawID return drawID
end end
---@param groupID number ---@param groupID number
@@ -677,9 +644,9 @@ do -- INIT DCS_UTIL
---@param location Vec3 ---@param location Vec3
---@return number markID ---@return number markID
function DCS_UTIL.AddMarkToGroup(groupID, text, location) function DCS_UTIL.AddMarkToGroup(groupID, text, location)
local nextId = DCS_UTIL.GetNextDrawID() local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToGroup(nextId, text, location, groupID, true, nil) trigger.action.markToGroup(drawID, text, location, groupID, true, nil)
return nextId return drawID
end end
---comment ---comment
@@ -687,9 +654,9 @@ do -- INIT DCS_UTIL
---@param location Vec3 ---@param location Vec3
---@return integer ---@return integer
function DCS_UTIL.AddMarkToAll(text, location) function DCS_UTIL.AddMarkToAll(text, location)
local nextId = DCS_UTIL.GetNextDrawID() local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToAll(nextId, text, location, true, nil) trigger.action.markToAll(drawID, text, location, true, nil)
return nextId return drawID
end end
---@param markId number ---@param markId number
@@ -738,7 +705,7 @@ do -- INIT DCS_UTIL
---@return number? id ---@return number? id
function DCS_UTIL.GetNeutralCountry() function DCS_UTIL.GetNeutralCountry()
for _, id in pairs(country.id) do for name, id in pairs(country.id) do
if coalition.getCountryCoalition(id) == DCS_UTIL.Coalition.NEUTRAL then if coalition.getCountryCoalition(id) == DCS_UTIL.Coalition.NEUTRAL then
return id return id
end end
@@ -768,8 +735,6 @@ do -- INIT DCS_UTIL
return false return false
end end
---@param groupName string
---@param offset number?
---@return boolean ---@return boolean
function DCS_UTIL.IsBingoFuel(groupName, offset) function DCS_UTIL.IsBingoFuel(groupName, offset)
if offset == nil then offset = 0 end if offset == nil then offset = 0 end
@@ -794,7 +759,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.GetPlayerGroupByGroupID(groupId) function DCS_UTIL.GetPlayerGroupByGroupID(groupId)
for i = 0, 2 do for i = 0, 2 do
local players = coalition.getPlayers(i) local players = coalition.getPlayers(i)
for _, unit in pairs(players) do for key, unit in pairs(players) do
if unit and unit:isExist() == true then if unit and unit:isExist() == true then
local group = unit:getGroup() local group = unit:getGroup()
if group and group:getID() == groupId then if group and group:getID() == groupId then
@@ -810,7 +775,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.GetPlayerUnitByID(unitID) function DCS_UTIL.GetPlayerUnitByID(unitID)
for i = 0, 2 do for i = 0, 2 do
local players = coalition.getPlayers(i) local players = coalition.getPlayers(i)
for _, unit in pairs(players) do for key, unit in pairs(players) do
if unit and unit:getID() == unitID then if unit and unit:getID() == unitID then
return unit return unit
end end
-7
View File
@@ -3,13 +3,6 @@ local Util = require("classes.util.Util")
local SpearheadConfig = require("classes.configuration.GlobalConfig") local SpearheadConfig = require("classes.configuration.GlobalConfig")
--- @alias LogLevel
--- | "DEBUG"
--- | "INFO"
--- | "WARN"
--- | "ERROR"
--- | "NONE"
---@type LogLevel ---@type LogLevel
local defaultLogLevel = "INFO" local defaultLogLevel = "INFO"
+3 -7
View File
@@ -1,12 +1,8 @@
---@class MissionEditingWarnings ---@class MissionEditingWarnings
local MissionEditingWarnings = {} local MissionEditingWarnings = {}
---@type table<number, string>
MissionEditingWarnings.warnings = {}
function MissionEditingWarnings.Add(warningMessage) function MissionEditingWarnings.Add(warningMessage)
table.insert(MissionEditingWarnings.warnings, warningMessage or "skip") table.insert(MissionEditingWarnings, warningMessage or "skip")
end end
---@param logger Logger ---@param logger Logger
@@ -16,12 +12,12 @@ function MissionEditingWarnings.WriteAll(logger)
return return
end end
if not MissionEditingWarnings.warnings or #MissionEditingWarnings.warnings == 0 then if not MissionEditingWarnings or #MissionEditingWarnings == 0 then
return return
end end
logger:warn("Mission Editor Warnings:") logger:warn("Mission Editor Warnings:")
for _, warning in ipairs(MissionEditingWarnings.warnings) do for _, warning in ipairs(MissionEditingWarnings) do
logger:warn("- " .. warning) logger:warn("- " .. warning)
end end
+19 -49
View File
@@ -4,7 +4,7 @@ do -- INIT UTIL
---splits a string in sub parts by separator ---splits a string in sub parts by separator
---@param input string ---@param input string
---@param separator string ---@param separator string
---@return Array<string> result list of strings ---@return table result list of strings
function UTIL.split_string(input, separator) function UTIL.split_string(input, separator)
if separator == nil then if separator == nil then
separator = " " separator = " "
@@ -22,7 +22,7 @@ do -- INIT UTIL
end end
---comment ---comment
---@param table table<any, any> ---@param table any
---@return number ---@return number
function UTIL.tableLength(table) function UTIL.tableLength(table)
if table == nil then return 0 end if table == nil then return 0 end
@@ -35,34 +35,23 @@ do -- INIT UTIL
---@param orig table ---@param orig table
---@return table copy ---@return table copy
function UTIL.deepCopyTable(orig) function UTIL.deepCopyTable(orig)
local orig_type = type(orig)
---@param original any local copy
---@return any if orig_type == 'table' then
local function deepCopy(original) copy = {}
local orig_type = type(original) for orig_key, orig_value in next, orig, nil do
---@type any copy[UTIL.deepCopyTable(orig_key)] = UTIL.deepCopyTable(orig_value)
local copy
if orig_type == 'table' then
original = original --[[@as table<any, any>]]
copy = {} --[[@as table<any, any>]]
for orig_key, orig_value in pairs(original) do
local copiedKey = deepCopy(orig_key) --[[@as any]]
copy[copiedKey] = deepCopy(orig_value)
end
setmetatable(copy, deepCopy(getmetatable(original)))
else -- number, string, boolean, etc
copy = original --[[@as any]]
end end
return copy setmetatable(copy, UTIL.deepCopyTable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end end
return copy
return deepCopy(orig)
end end
---Gets a random from the list ---Gets a random from the list
---@generic T ---@param list Array
---@param list Array<T> ---@return any @random element from the list
---@return T @random element from the list
function UTIL.randomFromList(list) function UTIL.randomFromList(list)
local max = #list local max = #list
@@ -76,13 +65,11 @@ do -- INIT UTIL
return list[random] return list[random]
end end
---@generic T ---@param list Array
---@param list Array<T>
---@param start number start ---@param start number start
---@param n number length ---@param n number length
---@return Array<T> ---@return Array
function UTIL.sublist(list, start, n) function UTIL.sublist(list, start, n)
---@type table<number, any>
local result = {} local result = {}
for i = start, n do for i = start, n do
result[#result + 1] = list[i] result[#result + 1] = list[i]
@@ -103,10 +90,9 @@ do -- INIT UTIL
end end
local function table_print(tt, indent, done) local function table_print(tt, indent, done)
done = done or {} --[[@as table<any, boolean>]] done = done or {}
indent = indent or 0 indent = indent or 0
if type(tt) == "table" then if type(tt) == "table" then
tt = tt --[[@as table<any, any>]]
local sb = {} local sb = {}
for key, value in pairs(tt) do for key, value in pairs(tt) do
table.insert(sb, string.rep(" ", indent)) -- indent it table.insert(sb, string.rep(" ", indent)) -- indent it
@@ -160,10 +146,10 @@ do -- INIT UTIL
---comment ---comment
---@param str string ---@param str string
---@param findableTable table<any,any> ---@param findableTable table
---@return boolean ---@return boolean
UTIL.startswithAny = function(str, findableTable) UTIL.startswithAny = function(str, findableTable)
for _, value in pairs(findableTable) do for key, value in pairs(findableTable) do
if type(value) == "string" and UTIL.startsWith(str, value) then return true end if type(value) == "string" and UTIL.startsWith(str, value) then return true end
end end
return false return false
@@ -252,13 +238,7 @@ do -- INIT UTIL
return ((vec1Norm.x * vec2Norm.x) + (vec1Norm.y * vec2Norm.y) + (vec1Norm.z * vec2Norm.z)) return ((vec1Norm.x * vec2Norm.x) + (vec1Norm.y * vec2Norm.y) + (vec1Norm.z * vec2Norm.z))
end end
---@param polygon Array<Vec2> of pairs { x, y }
---@param x number X location
---@param y number Y location
---@return boolean
local function isInComplexPolygon(polygon, x, y) local function isInComplexPolygon(polygon, x, y)
---@param poly Array<Vec2> of pairs { x, y }
---@return Array<{ x1: number, z1: number, x2: number, z2: number }>
local function getEdges(poly) local function getEdges(poly)
local result = {} local result = {}
for i = 1, #poly do for i = 1, #poly do
@@ -295,7 +275,6 @@ do -- INIT UTIL
---@param point Vec3 ---@param point Vec3
---@param zone SpearheadTriggerZone ---@param zone SpearheadTriggerZone
---@return boolean
function UTIL.is3dPointInZone(point, zone) function UTIL.is3dPointInZone(point, zone)
if zone.zone_type == "Polygon" and zone.verts then if zone.zone_type == "Polygon" and zone.verts then
if UTIL.IsPointInPolygon(zone.verts, point.x, point.z) == true then if UTIL.IsPointInPolygon(zone.verts, point.x, point.z) == true then
@@ -312,7 +291,6 @@ do -- INIT UTIL
---@param point Vec2 ---@param point Vec2
---@param zone SpearheadTriggerZone ---@param zone SpearheadTriggerZone
---@return boolean
function UTIL.is2dPointInZone(point, zone) function UTIL.is2dPointInZone(point, zone)
if zone.zone_type == "Polygon" and zone.verts then if zone.zone_type == "Polygon" and zone.verts then
if UTIL.IsPointInPolygon(zone.verts, point.x, point.y) == true then if UTIL.IsPointInPolygon(zone.verts, point.x, point.y) == true then
@@ -421,9 +399,7 @@ do -- INIT UTIL
if #points == 0 then return {} end if #points == 0 then return {} end
-- Simple clustering: group points that are within minSeparation of each other -- Simple clustering: group points that are within minSeparation of each other
---@type Array<Array<Vec2>>
local clusters = {} local clusters = {}
---@type table<integer, boolean>
local assigned = {} local assigned = {}
for i, p in ipairs(points) do for i, p in ipairs(points) do
@@ -464,8 +440,6 @@ do -- INIT UTIL
end end
---@param points Array<Vec2> ---@param points Array<Vec2>
---@param meters number
---@return Array<Vec2>
function UTIL.enlargeConvexHull(points, meters) function UTIL.enlargeConvexHull(points, meters)
if points == nil or #points == 0 then if points == nil or #points == 0 then
return {} return {}
@@ -543,10 +517,6 @@ function UTIL.GetTangentHullPointsFromOrigin(hull, origin)
return {} return {}
end end
---@param a Vec2
---@param b Vec2
---@param c Vec2
---@return number
local function orientation(a, b, c) local function orientation(a, b, c)
-- Returns >0 if c is to the left of ab, <0 if to the right, 0 if colinear -- Returns >0 if c is to the left of ab, <0 if to the right, 0 if colinear
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
+2 -1
View File
@@ -1,6 +1,7 @@
--Single player purpose --Single player purpose
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local DcsUtil = require("classes.util.DcsUtil")
local Database = require("classes.spearhead_db") local Database = require("classes.spearhead_db")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
@@ -46,7 +47,7 @@ local spawnManager = SpawnManager.new(spawnLogger)
local detectionLogger = Logger.new("DetectionManager", defaultLogLevel) local detectionLogger = Logger.new("DetectionManager", defaultLogLevel)
local detectionManager = DetectionManager.New(detectionLogger) local detectionManager = DetectionManager.New(detectionLogger)
GlobalCapManager.start(databaseManager, capConfig, detectionManager, defaultLogLevel, spawnManager) GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager)
local globalStageManager = GlobalStageManager.new(databaseManager, stageConfig, defaultLogLevel, spawnManager) local globalStageManager = GlobalStageManager.new(databaseManager, stageConfig, defaultLogLevel, spawnManager)
GlobalFleetManager.start(databaseManager) GlobalFleetManager.start(databaseManager)