Author SHA1 Message Date
dutchie031 446a98dc3b merged develop
PR Prerequisites / Lua Check (pull_request) Successful in 21s
PR Prerequisites / Bundles Without Errors (pull_request) Successful in 29s
2026-09-20 18:43:32 +02:00
dutchie031 8cacad84a1 merged develop
PR Prerequisites / Lua Check (pull_request) Successful in 21s
PR Prerequisites / Bundles Without Errors (pull_request) Failing after 24s
2026-09-20 18:35:43 +02:00
dutchie031 cbccedf710 updated pipeline
Publish Beta Release / build (push) Successful in 28s
2026-09-20 18:28:56 +02:00
dutchie031 e986887dd8 updated pipeline
Publish Release / build (push) Successful in 41s
2026-09-20 18:28:29 +02:00
dutchie031 9549e8c48e added lua check CI step (#49)
Publish Release / build (push) Failing after 3s
Reviewed-on: #49
Co-authored-by: dutchie031 <timrorije@gmail.com>
2026-09-20 16:08:04 +00:00
dutchie031 f3a38db5ca added comment 2026-09-08 16:13:37 +02:00
dutchie031 81d9aa7265 Updated preactivate logic 2026-09-08 16:12:38 +02:00
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
50 changed files with 767 additions and 390 deletions
+1 -1
View File
@@ -169,7 +169,7 @@ class Sidebar extends HTMLElement {
render() { render() {
var betaHtml = ''; var betaHtml = '';
if (true || window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') { if (window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>'; betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>';
} }
+83 -7
View File
@@ -26,14 +26,14 @@
<div class="content-wrapper"> <div class="content-wrapper">
<h1>Advanced Tutorial: Missions</h1> <h1>Advanced Tutorial: Missions</h1>
<div id="strike-region">
<h2 id="strike">Strike</h2> <h2 id="strike">Strike</h2>
<p> <p>
Naming: <span class="inline-lua"><span class="lua-variable">MISSION_STRIKE_[Name]</span></span> <br> Naming: <code-inline>MISSION_STRIKE_[Name]</code-inline> <br>
</p> </p>
<h3 id="specific_targets">Specific Targets</h3> <h3 id="specific_targets">Specific Targets</h3>
<p>TBD</p> <p>TBD</p>
<h3 id="scenery_targets">Scenery Targets</h3> <h3 id="scenery_targets">Scenery Targets</h3>
<p> <p>
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br> Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
@@ -41,18 +41,23 @@
</p> </p>
<note-box type="info" title="Implementation details"> <note-box type="info" title="Implementation details">
Spearhead detects scenery targets by the trigger zone name. A zone named <strong>scenerytarget_[freeform]</strong> or Spearhead detects scenery targets by the trigger zone name. A zone named
<strong>scenerytargets</strong> (case-insensitive) will be scanned and any scenery objects that has the attribute "Buildings" inside the zone <strong>scenerytarget_[freeform]</strong> or
<strong>scenerytargets</strong> (case-insensitive) will be scanned and any scenery objects that
has the attribute "Buildings" inside the zone
will be added as mission scenery targets. will be added as mission scenery targets.
</note-box> </note-box>
<p> <p>
Scenery targets are treated like mission targets: they are included in the mission completion calculation Scenery targets are treated like mission targets: they are included in the mission completion
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or resume. calculation
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or
resume.
</p> </p>
<note-box type="warning" title="Changing IDs"> <note-box type="warning" title="Changing IDs">
Due to Object IDs changing when maps are updated and edited the "Assign as..." functionality does not work for scenery targets. Due to Object IDs changing when maps are updated and edited the "Assign as..." functionality
does not work for scenery targets.
Hence the need to use trigger zones to define them. Hence the need to use trigger zones to define them.
</note-box> </note-box>
@@ -60,6 +65,77 @@
<p>Here's two images showing how a bridge is added to a mission.</p> <p>Here's two images showing how a bridge is added to a mission.</p>
<img src="/img/bridge.png" style="max-height: 35remrem;" /> <img src="/img/bridge.png" style="max-height: 35remrem;" />
</div> </div>
<div id="cas-region">
<h2 id="cas">CAS</h2>
<p>
Naming: <code-inline>MISSION_CAS_[Name]</code-inline> <br>
</p>
<h3 id="BattleManager">Battle Manager</h3>
<p>TBD</p>
</div>
<div id="dead-region">
<h2 id="dead">DEAD</h2>
<p>
Naming: <code-inline>MISSION_DEAD_[Name]</code-inline> <br>
Naming: <code-inline>MISSION_SAM_[Name]</code-inline> <br>
</p>
<h3 id="sam-vs-dead">SAM vs DEAD</h3>
<p>TBD</p>
</div>
<div id="bai-region">
<h2 id="bai">BAI</h2>
<p>
Naming: <code-inline>MISSION_BAI_[Name]</code-inline> <br>
</p>
</div>
<div id="deepstrike-region">
<h2 id="deepstrike">DEEPSTRIKE</h2>
<p>
Naming: <code-inline>MISSION_DEEPSTRIKE_[Name]</code-inline> <br>
<br>
When you, as a mission editor, want to create a mission that's further away from the current front-line you can use the Deep Strike mission type. <br>
This allows you to plan missions that require more strategic positioning and timing. <br>
</p>
<h3 id="activation">Activation</h3>
<p>
Deep Strike missions are activated when the Stage in which they are located becomes "PreActive" or "Active". <br>
"PreActive" is when either the current active StageNumber is a certain number of stages away OR the stage has "CAP" airbases active . <br>
<code-inline>SpearheadConfig.StageConfig.preactivateStage</code-inline> changes the configuration. <br>
<br>
For example: <br>
If the current active stage is <code-inline>MissionStage_2_[name]</code-inline> and preActivate config is set to 2. <br>
Then both <code-inline>MissionStage_3_[name]</code-inline> and <code-inline>MissionStage_4_[name]</code-inline> would become "PreActive".
</p>
<h3 id="primary-vs-secondary">Make Primary</h3>
<p>
By default DEEPSTRIKE missions are "Secondary" missions when spawned as a pre-activated stage.<br>
Then when the stage they are in is "Active", the mission becomes a "Primary" mission. <br>
<br>
If you want to make it a "Primary" mission from the start, you can overwrite it.<br>
Click the mission's trigger zone. Add a property named <code-inline>primary</code-inline> and set it to <code-inline>true</code-inline>.
This will make the mission "Primary" from the start. <br>
<br>
If you want it to "Secondary" even when its stage is the active stage, you can set "primary" to false. <br>
<br>
If you want to have it be "Secondary" when its stage is "Pre-Active" and "primary" when its stage is "Active" you don't have to do anything.
</p>
<h3 id="future-additions">Future</h3>
<p>
In the future we're thinking it would be cool to make the Deep Strike missions
be more integrated with the CAP airbases and/or logistics. <br>
Eg. Completing a DEEPSTRIKE mission would disable CAP for X amount of minutes after completion. <br>
This still needs to be refined, but we've already have ideas.
</p>
</div>
</div>
</div> </div>
</main> </main>
+1 -1
View File
@@ -8,7 +8,7 @@
--color-bg-hover: rgba(120, 140, 255, 0.12); /* More visible hover */ --color-bg-hover: rgba(120, 140, 255, 0.12); /* More visible hover */
--color-bg-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */ --color-bg-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */
--color-text-primary: #a5a5a5; /* Brighter text */ --color-text-primary: #b4b3b3; /* Brighter text */
--color-text-secondary: #bfc8d8; /* Lighter secondary text */ --color-text-secondary: #bfc8d8; /* Lighter secondary text */
--color-text-accent: #7ecbff; /* Brighter accent */ --color-text-accent: #7ecbff; /* Brighter accent */
--color-text-light: #ffffff; --color-text-light: #ffffff;
+66 -2
View File
@@ -24,7 +24,7 @@ jobs:
luaVersion: ${{ env.LUA_VERSION }} luaVersion: ${{ env.LUA_VERSION }}
- name: Compile Spearhead - name: Compile Spearhead
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-action@action/v0 uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
with: with:
source-root: ./src source-root: ./src
output-file: ./output/spearhead.lua output-file: ./output/spearhead.lua
@@ -38,6 +38,39 @@ jobs:
- name: Install luacheck - name: Install luacheck
run: luarocks 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 - name: Lint Compiled Output
run: luacheck ./output/spearhead.lua run: luacheck ./output/spearhead.lua
@@ -58,6 +91,9 @@ jobs:
with: with:
destination-path: "./.vscode/lua-addons" ##same as local so .luarc.json can be the same 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 - name: Install lua-language-server
run: | run: |
mkdir -p ./luals mkdir -p ./luals
@@ -69,8 +105,36 @@ jobs:
- name: Verify Lua Language Server Installation - name: Verify Lua Language Server Installation
run: ./luals/bin/lua-language-server --version 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 - name: Run Lua Language Server
run: ./luals/bin/lua-language-server --check ./src --checklevel=Warning 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 Release name: Publish Beta 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-action@action/v0 uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
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-action@action/v0 uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
with: with:
source-root: ./src source-root: ./src
output-file: ./output/spearhead.lua output-file: ./output/spearhead.lua
+21 -10
View File
@@ -1,21 +1,30 @@
{ {
"Lua": { "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": { "runtime": {
"version": "Lua 5.1" "version": "Lua 5.1"
}, },
"workspace": { "workspace": {
"checkThirdParty": false "checkThirdParty": false,
"maxPreload": 1600,
"preloadFileSize": 1000,
"ignoreDir": [ "config.lua" ]
}, },
"diagnostics": { "diagnostics": {
"enable": true, "enable": true,
"globals": [ "globals": [
"lfs" "lfs"
], ],
"severity": { "groupSeverity": {
"undefined-field": "Warning", "duplicate": "Warning",
"unused-local": "Warning", "global":"Warning",
"unused-function": "Warning" "luadoc": "Warning",
"redefined": "Warning",
"strong": "Warning",
"type-check": "Warning",
"unbalanced" : "Warning",
"ambiguity": "Warning",
"strict": "Warning",
"unused": "Warning"
}, },
"groupFileStatus": { "groupFileStatus": {
"ambiguity": "Any", "ambiguity": "Any",
@@ -27,8 +36,11 @@
"strict": "Any", "strict": "Any",
"type-check": "Any", "type-check": "Any",
"unbalanced": "Any", "unbalanced": "Any",
"unused": "Any" "unused": "Any",
} "strong": "Any"
},
"ignoredFiles": "Opened",
"libraryFiles": "Opened"
}, },
"hint": { "hint": {
"enable": false "enable": false
@@ -37,4 +49,3 @@
"enable": true "enable": true
} }
} }
}
+5
View File
@@ -19,6 +19,11 @@
Now enabled the mission editor to add a custom briefing to the "Buildable" missions. Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
#44 #44
- Issue: #42
DEEPSTRIKE now added as a possible feature.
This enables to have missions active in "Pre-Activated" Stages.
#48
### Bug Fixes ### Bug Fixes
## [0.13.0] 2026-08 ## [0.13.0] 2026-08
-47
View File
@@ -1,47 +0,0 @@
---@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
@@ -6,7 +6,9 @@ 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 initiated = false local initiated = false
@@ -5,7 +5,7 @@ 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 ---@class AirGroup : OnUnitLostListener, OnGroupRTBListener, OnGroupRTBInTenListener, OnLandEventListener, OnGroupOnStationListener
---@field protected _logger Logger ---@field protected _logger Logger
---@field protected _groupName string ---@field protected _groupName string
---@field protected _groupType AirGroupType ---@field protected _groupType AirGroupType
@@ -107,8 +107,9 @@ 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
for _, wp in pairs(mission.params.route.points) do local routePoints = mission.params.route.points --[[@as Array<Vec2> ]]
if wp.x and wp.y then for _, wp in pairs(routePoints) do
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
@@ -198,6 +199,8 @@ 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
@@ -263,6 +266,7 @@ 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
@@ -34,9 +34,10 @@ 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[stageID] return self._targetZoneIdPerStage[tostring(stageID)]
end end
---@return string? ---@return string?
@@ -60,6 +60,7 @@ 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)
@@ -68,9 +69,10 @@ 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[stageID] return self._targetZoneIdPerStage[tostring(stageID)]
end end
---@return string? ---@return string?
@@ -91,6 +93,9 @@ 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
@@ -102,6 +107,7 @@ 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
@@ -220,7 +226,8 @@ 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
local mission = nil ---@type table
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(
@@ -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<number, string> ---@field _targetZoneIdPerStage table<string, string>
---@field _currentTargetZoneID string? ---@field _currentTargetZoneID string?
local SweepGroup = {} local SweepGroup = {}
SweepGroup.__index = SweepGroup SweepGroup.__index = SweepGroup
@@ -26,9 +26,10 @@ 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[stageID] return self._targetZoneIdPerStage[tostring(stageID)]
end end
---@return string? ---@return string?
@@ -8,6 +8,7 @@ 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)
@@ -24,6 +25,7 @@ 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
@@ -32,6 +34,7 @@ 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
@@ -43,6 +46,7 @@ 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
@@ -56,7 +60,8 @@ function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20 return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
end end
---@return Array<string> ---@param coalitionSide CoalitionSide
---@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
@@ -56,7 +56,9 @@ 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
@@ -102,11 +104,10 @@ function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
self._logger:debug("RunwayBombingTracker:OnWeaponImpact") self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
local warhead = weaponDesc.warhead local warhead = weaponDesc.warhead --[[@as table]]
local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass local explosiveMass = (warhead.explosiveMass or warhead.shapedExplosiveMass) --[[@as number]]
for _, 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
+8
View File
@@ -7,6 +7,7 @@ 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",
} }
@@ -53,7 +54,10 @@ 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
@@ -84,6 +88,7 @@ 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,6 +130,7 @@ 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),
@@ -152,6 +158,7 @@ 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),
@@ -178,6 +185,7 @@ 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,6 +11,7 @@ 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()
@@ -92,7 +93,7 @@ end
---@param currentPoint Vec2 ---@param currentPoint Vec2
---@param targetPosition Vec2 ---@param targetPosition Vec2
---@param targetUnit Unit ---@param targetUnit Unit
---@param airbase any ---@param airbase Airbase
---@param config any ---@param config any
---@param speed number? ---@param speed number?
---@param alt number? ---@param alt number?
+5
View File
@@ -7,6 +7,7 @@ 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",
@@ -113,6 +114,7 @@ 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)
@@ -147,6 +149,7 @@ 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 {
@@ -170,6 +173,8 @@ 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,6 +7,7 @@ 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",
} }
@@ -49,7 +50,9 @@ 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
@@ -80,6 +83,7 @@ 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 }
@@ -121,6 +125,7 @@ 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,6 +21,10 @@ 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 -1
View File
@@ -21,7 +21,7 @@ function DebugMenu:RegisterMenus()
missionCommands.addSubMenu(menuName, {}) missionCommands.addSubMenu(menuName, {})
local refresh = function(params) local refresh = function(params)
local selfA = params.self local selfA = params.self --[[@as DebugMenu]]
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
@@ -1,19 +0,0 @@
do -- mission aliases
--- @alias LogLevel
--- | "DEBUG"
--- | "INFO"
--- | "WARN"
--- | "ERROR"
--- | "NONE"
---@class Array<T>: { [integer]: T }
end
---@class KeyValuePair
---@field key string
---@field value any
+13 -7
View File
@@ -10,7 +10,7 @@ local SpearheadEvents = require("classes.spearhead_events")
---@field private fleetNameIdentifier string ---@field private fleetNameIdentifier string
---@field private targetZonePerStage table<string, string> ---@field private targetZonePerStage table<string, string>
---@field private currentTargetZone string ---@field private currentTargetZone string
---@field private pointsPerZone table<string, {pointA: Vec2, pointB: Vec2}> ---@field private pointsPerZone table<string, {pointA: Vec3, pointB: Vec3}>
local FleetGroup = {} local FleetGroup = {}
FleetGroup.__index = FleetGroup FleetGroup.__index = FleetGroup
@@ -78,7 +78,7 @@ function FleetGroup.new(fleetGroupName, database, logger)
return nil, nil return nil, nil
end end
if Util.startswith(namePart, "%[") == true then if Util.startsWith(namePart, "%[") == true then
namePart = Util.split_string(namePart, "[")[1] namePart = Util.split_string(namePart, "[")[1]
end end
@@ -108,7 +108,13 @@ function FleetGroup.new(fleetGroupName, database, logger)
for i = first, second do for i = first, second do
self.targetZonePerStage[tostring(i)] = zoneName self.targetZonePerStage[tostring(i)] = zoneName
end end
self.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} } ---@type {pointA: Vec3, pointB: Vec3}
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
@@ -125,10 +131,10 @@ function FleetGroup.new(fleetGroupName, database, logger)
end end
local SetTaskAsync = function(input, _) local SetTaskAsync = function(input, _)
local targetZone = input.targetZone local targetZone = input.targetZone --[[@as string]]
local task = input.task local task = input.task --[[@as table]]
local groupName = input.groupName local groupName = input.groupName --[[@as string]]
local l_logger = input.logger local l_logger = input.logger --[[@as Logger]]
local group = Group.getByName(groupName) local group = Group.getByName(groupName)
if group then if group then
@@ -15,7 +15,7 @@ GlobalFleetManager.start = function(database)
local all_groups = MizGroupsManager.getAllGroupNames() local all_groups = MizGroupsManager.getAllGroupNames()
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)
+30 -7
View File
@@ -15,23 +15,45 @@ MizGroupsManager._groupNames = {}
MizGroupsManager._spawnTemplateData = {} MizGroupsManager._spawnTemplateData = {}
do --init do --init
for _, coalition_data in pairs(env.mission.coalition) do
---@param coalition_name string
---@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_data in pairs(coalition_data.country) do
for category_name, categorydata in pairs(country_data) do for _, category_name in pairs(DcsUtil.getAllGroupCategoryNames()) do
local category_id = DcsUtil.stringToGroupCategory(category_name) local category_id = DcsUtil.stringToGroupCategory(category_name)
if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then local groups_data = getGroupsData(country_data, category_name)
for _, group in pairs(categorydata.group) do
local name = group.name if category_id ~= nil and type(groups_data) == "table" and groups_data.group ~= nil and type(groups_data.group) == "table" then
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] local unit = group.units[1] --[[@as table]]
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 name = unit.name --[[@as string]]
else else
env.error("Group " .. name .. " has no units, skipping it.") env.error("Group " .. name .. " has no units, skipping it.")
skippable = true skippable = true
@@ -71,6 +93,7 @@ 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]
+13 -18
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
@@ -125,37 +125,31 @@ 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
for _, unit in pairs(spawnTemplate["units"]) do local units = spawnTemplate["units"] --[[@as Array<table>]]
local name = unit["name"] for _, unit in pairs(units) do
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
unit["payload"]["pylons"] = {} local payload = unit["payload"] --[[@as table]]
payload["pylons"] = {} --[[@as table]]
end end
end end
if unit["parking"] then if unit["parking"] then
unit["parking_landing"] = unit["parking"] unit["parking_landing"] = unit["parking"] --[[@as number]]
end end
if unit["parking_id"] then if unit["parking_id"] then
unit["parking_landing_id"] = unit["parking_id"] unit["parking_landing_id"] = unit["parking_id"] --[[@as number]]
end end
end end
@@ -169,7 +163,6 @@ 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
@@ -210,9 +203,11 @@ 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
spawnTemplate["units"][1]["x"] = persistentState.pos.x local units = spawnTemplate["units"] --[[@as Array<table>]]
spawnTemplate["units"][1]["y"] = persistentState.pos.z local firstUnit = units[1] --[[@as table]]
spawnTemplate["units"][1]["heading"] = persistentState.heading or 0 firstUnit["x"] = persistentState.pos.x --[[@as number]]
firstUnit["y"] = persistentState.pos.z --[[@as number]]
firstUnit["heading"] = persistentState.heading or 0 --[[@as number]]
end end
end end
end end
+14 -4
View File
@@ -150,7 +150,7 @@ do
for file in lfs.dir(dir) do for file in lfs.dir(dir) do
local split = Util.split_string(file, ".") local split = Util.split_string(file, ".")
local doesStartWith = Util.startswith(file, startsWith, true) local doesStartWith = Util.startsWith(file, startsWith, true)
if split and #split > 0 and split[#split] == EXTENSION and doesStartWith == true then if split and #split > 0 and split[#split] == EXTENSION and doesStartWith == true then
local numberString = split[#split-1] local numberString = split[#split-1]
local number = tonumber(numberString) local number = tonumber(numberString)
@@ -183,11 +183,14 @@ 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 and #split < 3 then if not split or #split < 3 then
split = split or {}
split[#split+1] = "0" split[#split+1] = "0"
split[#split+1] = "spearhead" split[#split+1] = "spearhead"
end end
@@ -200,7 +203,10 @@ do
fileName = table.concat(split, ".") fileName = table.concat(split, ".")
end end
if SpearheadConfig.Persistence.directory ~= nil then if SpearheadConfig.Persistence.directory 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
@@ -210,6 +216,10 @@ 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, ".")
+42 -18
View File
@@ -69,6 +69,7 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@field descriptionLocation Vec2? ---@field descriptionLocation Vec2?
---@field dependsOn Array<string> ---@field dependsOn Array<string>
---@field completeAt number? ---@field completeAt number?
---@field primaryOverwrite boolean?
---@class FarpZoneData ---@class FarpZoneData
---@field groups Array<string> ---@field groups Array<string>
@@ -211,10 +212,12 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do for _, 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 _, 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
layer_object = layer_object --[[@as TextBox]]
local number = tonumber(layer_object.text) local number = tonumber(layer_object.text)
airbaseData.buildingKilos = number airbaseData.buildingKilos = number
end end
@@ -224,6 +227,7 @@ function Database.New(Logger)
end end
end end
end end
end
---@type table<integer, boolean> ---@type table<integer, boolean>
do -- custom drawings do -- custom drawings
@@ -231,7 +235,7 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do for _, 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 _, 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)
if stageDrawing then if stageDrawing then
@@ -259,10 +263,12 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do for _, 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 _, 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
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
stageData.StageBriefing = description stageData.StageBriefing = description
@@ -273,6 +279,7 @@ function Database.New(Logger)
end end
end end
end end
end
-- fill blue sams -- fill blue sams
for _, blueSamStageName in pairs(self._tables.BlueSams) do for _, blueSamStageName in pairs(self._tables.BlueSams) do
@@ -468,6 +475,7 @@ 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()
@@ -484,7 +492,7 @@ end
local getAvailableCAPGroups = function() local getAvailableCAPGroups = function()
local result = {} local result = {}
for name, value in pairs(is_group_taken) do for name, value in pairs(is_group_taken) do
if value == false and Util.startswith(name, "CAP") then if value == false and Util.startsWith(name, "CAP") then
table.insert(result, name) table.insert(result, name)
end end
end end
@@ -587,11 +595,11 @@ function Database:loadCapUnits()
for _, groupName in pairs(groups) do for _, groupName in pairs(groups) do
is_group_taken[groupName] = true is_group_taken[groupName] = true
if Util.startswith(groupName, "CAP_A", true) or Util.startswith(groupName, "CAP_B", true) then if Util.startsWith(groupName, "CAP_A", true) or Util.startsWith(groupName, "CAP_B", true) then
table.insert(baseData.CapGroups, groupName) table.insert(baseData.CapGroups, groupName)
elseif Util.startswith(groupName, "CAP_I", true) then elseif Util.startsWith(groupName, "CAP_I", true) then
table.insert(baseData.InterceptGroups, groupName) table.insert(baseData.InterceptGroups, groupName)
elseif Util.startswith(groupName, "CAP_S", true) then elseif Util.startsWith(groupName, "CAP_S", true) then
table.insert(baseData.SweepGroups, groupName) table.insert(baseData.SweepGroups, groupName)
end end
end end
@@ -614,7 +622,7 @@ function Database:loadBlueSamUnits()
local triggerZone = DcsUtil.getZoneByName(blueSamZone) local triggerZone = DcsUtil.getZoneByName(blueSamZone)
if triggerZone then if triggerZone then
for _, kvPair in pairs(triggerZone.properties) do for _, kvPair in pairs(triggerZone.properties) do
if kvPair.key and Util.startswith(kvPair.key, "buildable") then if kvPair.key and Util.startsWith(kvPair.key, "buildable") then
local number = tonumber(kvPair.value) local number = tonumber(kvPair.value)
if number and number > 0 then if number and number > 0 then
samData.buildingKilos = number samData.buildingKilos = number
@@ -625,8 +633,9 @@ function Database:loadBlueSamUnits()
for _, layer_object in pairs(layer.objects) do for _, 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 triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if layer_object.primitiveType == "TextBox" and 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
@@ -702,9 +711,9 @@ function Database:LoadZoneData(missionZoneName)
if triggerZone and triggerZone.properties then if triggerZone and triggerZone.properties then
for _, kvPair in pairs(triggerZone.properties) do for _, kvPair in pairs(triggerZone.properties) do
local key = kvPair.key local key = kvPair.key
if Util.startswith(key, "dependson", true) == true then if Util.startsWith(key, "dependson", true) == true then
table.insert(self._tables.MissionZoneData[missionZoneName].dependsOn, kvPair.value) table.insert(self._tables.MissionZoneData[missionZoneName].dependsOn, kvPair.value)
elseif Util.startswith(key, "completeat") == true then elseif Util.startsWith(key, "completeat") == true then
local value = tonumber(kvPair.value) local value = tonumber(kvPair.value)
if value then if value then
if value > 1 and value <= 100 then if value > 1 and value <= 100 then
@@ -714,6 +723,14 @@ function Database:LoadZoneData(missionZoneName)
end end
self._tables.MissionZoneData[missionZoneName].completeAt = value self._tables.MissionZoneData[missionZoneName].completeAt = value
end end
elseif Util.startsWith(key, "primary", true) == true then
if type(kvPair.value) == "string" then
if kvPair.value == "true" then
self._tables.MissionZoneData[missionZoneName].primaryOverwrite = true
elseif kvPair.value == "false" then
self._tables.MissionZoneData[missionZoneName].primaryOverwrite = false
end
end
end end
end end
end end
@@ -725,8 +742,9 @@ function Database:LoadZoneData(missionZoneName)
for _, layer_object in pairs(layer.objects) do for _, 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 triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if layer_object.primitiveType == "TextBox" and 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
@@ -769,7 +787,7 @@ function Database:loadFarpData()
local triggerZone = DcsUtil.getZoneByName(farpZone) local triggerZone = DcsUtil.getZoneByName(farpZone)
if triggerZone then if triggerZone then
for _, kvPair in pairs(triggerZone.properties) do for _, kvPair in pairs(triggerZone.properties) do
if kvPair.key and Util.startswith(kvPair.key, "buildable", true) == true then if kvPair.key and Util.startsWith(kvPair.key, "buildable", true) == true then
local number = tonumber(kvPair.value) local number = tonumber(kvPair.value)
if number and number > 0 then if number and number > 0 then
farpzoneData.buildingKilos = number farpzoneData.buildingKilos = number
@@ -781,8 +799,9 @@ function Database:loadFarpData()
for _, layer_object in pairs(layer.objects) do for _, 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 triggerZone and Util.is2dPointInZone(vec2, triggerZone) then if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then layer_object = layer_object --[[@as TextBox]]
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
farpzoneData.briefing = description farpzoneData.briefing = description
@@ -950,6 +969,7 @@ 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]
@@ -957,6 +977,7 @@ 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]
@@ -964,13 +985,14 @@ 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 table result airbase Names ---@return Array<string> 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
@@ -984,6 +1006,7 @@ 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]
@@ -999,6 +1022,7 @@ 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 -17
View File
@@ -32,7 +32,9 @@ 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)
@@ -47,6 +49,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)
@@ -110,6 +113,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 _, err = pcall(function()
@@ -122,11 +128,14 @@ do
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 table table with function OnUnitLanded(self, initiatorUnit, airbase) ---@param landListener OnLandEventListener 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")
@@ -162,6 +171,11 @@ 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
@@ -197,11 +211,15 @@ 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 table object with OnGroupRTBInTen(self, groupName) ---@param handlingObject OnGroupRTBInTenListener 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")
@@ -234,10 +252,17 @@ 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")
@@ -270,15 +295,18 @@ 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 table object with OnPlayerEntersUnit(self, unit) ---@param listener OnPlayerEnterUnitListener 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
@@ -315,8 +343,8 @@ 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 local unit = event.initiator --[[@as Unit]]
local airbase = event.place local airbase = event.place --[[@as Airbase]]
if unit ~= nil then if unit ~= nil then
local name = unit:getName() local name = unit:getName()
if onLandEventListeners[name] then if onLandEventListeners[name] then
@@ -336,7 +364,7 @@ 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 local object = event.initiator --[[@as Unit]]
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())
@@ -355,15 +383,11 @@ do
end end
end end
if event.id == world.event.S_EVENT_EJECTION then
end
if event.id == world.event.S_EVENT_SHOT then if event.id == world.event.S_EVENT_SHOT then
local shooter = event.initiator local shooter = event.initiator --[[@as Unit]]
local weapon = event.weapon local weapon = event.weapon --[[@as Weapon]]
local target = event.target local target = event.target --[[@as Unit?]]
triggerWeaponFired(shooter, weapon, target) triggerWeaponFired(shooter, weapon, target)
end end
@@ -372,10 +396,14 @@ do
Persistence.UpdateNow() Persistence.UpdateNow()
end end
---@type table<string, boolean>
local AI_GROUPS = {} local AI_GROUPS = {}
---@param unit Unit
---@param _ any
local function CheckAndTriggerSpawnAsync(unit, _) local function CheckAndTriggerSpawnAsync(unit, _)
---@param checkUnit Unit
---@return boolean, string?
local function isPlayer(checkUnit) local function isPlayer(checkUnit)
if checkUnit == nil then return false, "unit is nil" end 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 checkUnit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
@@ -405,8 +433,6 @@ do
if isPlayer(unit) == true then if isPlayer(unit) == true then
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
+13 -5
View File
@@ -8,6 +8,7 @@ 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",
} }
@@ -53,8 +54,9 @@ do --setup route util
---comment ---comment
---@param groupName string ---@param groupName string
---@param position table { x, y} ---@param position Vec3
---@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
@@ -181,7 +183,7 @@ do --setup route util
end end
---comment ---comment
---@param position table { x, y} ---@param position Vec3
---@param altitude number ---@param altitude number
---@param speed number ---@param speed number
---@param childTasks table ---@param childTasks table
@@ -210,7 +212,8 @@ 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 table { x, z } ---@param capPoint Vec3
---@param racetrackSecondPoint Vec3?
---@param altitude number ---@param altitude number
---@param speed number ---@param speed number
---@param durationOnStation number ---@param durationOnStation number
@@ -258,7 +261,8 @@ 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()
local points = {} ---@type Array<table>
local points
if racetrackSecondPoint == nil then if racetrackSecondPoint == nil then
points = { points = {
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks), [1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
@@ -304,6 +308,7 @@ 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
@@ -393,6 +398,9 @@ 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",
@@ -461,7 +469,7 @@ do --setup route util
} }
} }
} }
}, "" }, nil
end end
end end
@@ -327,12 +327,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
groups[group:getID()] = group table.insert(groups, group)
end end
end end
@@ -369,8 +370,11 @@ 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"
@@ -403,7 +407,9 @@ function GlobalStageManager:PrintMermaidStage()
"#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
@@ -430,6 +436,7 @@ function GlobalStageManager:PrintMermaidStage()
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 .. "]"
@@ -496,7 +503,7 @@ function GlobalStageManager:PrintMermaidStage()
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] local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
-- 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
@@ -528,7 +535,7 @@ function GlobalStageManager:PrintMermaidStage()
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] local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
-- 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
@@ -91,9 +91,9 @@ function SpearheadGroup:GetCoalition()
end end
---comment ---comment
---@return table result list of objects ---@return Array<Unit|StaticObject> 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)
@@ -113,6 +113,7 @@ 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()
@@ -142,6 +143,7 @@ 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()
@@ -37,6 +37,7 @@ 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
@@ -104,6 +105,7 @@ function BuildableZone:OnCrateDroppedOff(_, 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
+12 -9
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<number, Array<Stage>> ---@field private _stagesInLaneByIndex table<string, Array<Stage>>
---@field private _chapterStarts table<number, boolean>? ---@field private _chapterStarts table<string, boolean>?
---@field private _stageLaneState StageLaneState ---@field private _stageLaneState StageLaneState
---@field private _maxStageIndex number ---@field private _maxStageIndex number
local StageLane = {} local StageLane = {}
@@ -16,6 +16,7 @@ StageLane.__index = StageLane
---| "BetweenChapters" ---| "BetweenChapters"
---| "Completed" ---| "Completed"
---@type string?
StageLane.DefaultLaneKey = nil StageLane.DefaultLaneKey = nil
function StageLane.New(laneIdentifier) function StageLane.New(laneIdentifier)
@@ -37,21 +38,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[stageIndex] then if not self._stagesInLaneByIndex[tostring(stageIndex)] then
self._stagesInLaneByIndex[stageIndex] = {} self._stagesInLaneByIndex[tostring(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[stageIndex], stage) table.insert(self._stagesInLaneByIndex[tostring(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[stageNumber] local stages = self._stagesInLaneByIndex[tostring(stageNumber)]
if not stages then if not stages then
return nil return nil
end end
@@ -88,7 +89,7 @@ end
---@param stageNumber number ---@param stageNumber number
function StageLane:SetActiveStageIndex(stageNumber) function StageLane:SetActiveStageIndex(stageNumber)
if self._stagesInLaneByIndex[stageNumber] == nil then if self._stagesInLaneByIndex[tostring(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
@@ -108,6 +109,7 @@ 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
@@ -123,13 +125,14 @@ 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)
self._chapterStarts = self._chapterStarts or {} if self._chapterStarts == nil then self._chapterStarts = {} end
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
@@ -158,7 +161,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[stageIndex] return self._stagesInLaneByIndex[tostring(stageIndex)]
end end
---@return number? ---@return number?
@@ -162,6 +162,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
---comment ---comment
---@param selfA Stage ---@param selfA Stage
---@param time number? ---@param time number?
---@return number|nil
self.CheckContinuousAsync = function (selfA, time) self.CheckContinuousAsync = function (selfA, time)
selfA:CheckAndUpdateSelf() selfA:CheckAndUpdateSelf()
@@ -325,7 +326,9 @@ 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)
@@ -432,6 +435,12 @@ function Stage:PreActivate()
end end
end end
for _, mission in pairs(self._db.missions) do
if mission and mission.missionType == "DEEPSTRIKE" then
mission:SpawnInactive() --Not Really "inactive"
end
end
for _, airbase in pairs(self._db.airbases) do for _, airbase in pairs(self._db.airbases) do
airbase:ActivateRedStage() airbase:ActivateRedStage()
end end
@@ -674,6 +683,7 @@ function Stage:ActivateBlueStage()
end end
---@param selfA Stage ---@param selfA Stage
---@return number|nil
local ActivateBlueAsync = function(selfA) local ActivateBlueAsync = function(selfA)
pcall(function() pcall(function()
selfA:MarkStage() selfA:MarkStage()
@@ -54,7 +54,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
@@ -125,14 +125,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]
@@ -274,6 +274,7 @@ 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
@@ -36,6 +36,7 @@ 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
@@ -148,7 +149,7 @@ end
---@return number ---@return number
function BattleManager:getBestAmmo(unit) function BattleManager:getBestAmmo(unit)
local ammo = unit:getAmmo() local ammo = unit:getAmmo() --[[@as table<number, table>]]
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
@@ -162,9 +163,9 @@ function BattleManager:getBestAmmo(unit)
end end
end end
local entry = Util.randomFromList(shells) local entry = Util.randomFromList(shells) --[[@as table]]
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 local caliber = entry.desc.warhead.caliber --[[@as number]]
if caliber > 50 then if caliber > 50 then
return 258503344128, 1 return 258503344128, 1
else else
@@ -209,6 +210,7 @@ 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)
@@ -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 ---@class MissionCommandsHelper : OnPlayerEnterUnitListener
---@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
@@ -210,7 +210,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do for code, enabled in pairs(self.enabledByCode) do
if enabled == true then if enabled == true then
local mission = self.missionsByCode[code] local mission = self.missionsByCode[code]
if mission and mission:getState() == "ACTIVE" and mission.priority == "primary" then if mission and mission:getState() == "ACTIVE" and mission:GetPriority() == "primary" then
table.insert(primaryMissions, mission) table.insert(primaryMissions, mission)
end end
end end
@@ -230,7 +230,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do for code, enabled in pairs(self.enabledByCode) do
if enabled == true then if enabled == true then
local mission = self.missionsByCode[code] local mission = self.missionsByCode[code]
if mission and mission:getState() == "ACTIVE" and mission.priority == "secondary" then if mission and mission:getState() == "ACTIVE" and mission:GetPriority() == "secondary" then
table.insert(secondaryMissions, mission) table.insert(secondaryMissions, mission)
end end
end end
@@ -345,7 +345,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do for code, enabled in pairs(self.enabledByCode) do
if enabled == true then if enabled == true then
local mission = self.missionsByCode[code] local mission = self.missionsByCode[code]
if mission and mission.priority == "primary" then if mission and mission:GetPriority() == "primary" then
table.insert(primaryMissions, mission) table.insert(primaryMissions, mission)
end end
end end
@@ -369,12 +369,12 @@ 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
local mission = self.missionsByCode[code] local mission = self.missionsByCode[code]
if mission and mission.priority == "secondary" then if mission and mission:GetPriority() == "secondary" then
table.insert(secondaryMissions, mission) table.insert(secondaryMissions, mission)
end end
end end
@@ -76,7 +76,7 @@ local SupplyConfigHelper = {}
---@return SupplyConfig? ---@return SupplyConfig?
function SupplyConfigHelper.fromObjectName(name) function SupplyConfigHelper.fromObjectName(name)
for configName, config in pairs(SupplyConfig) do for configName, config in pairs(SupplyConfig) do
if Util.startswith(name, configName, true) == true then if Util.startsWith(name, configName, true) == true then
return config return config
end end
end end
@@ -84,6 +84,7 @@ function SupplyConfigHelper.fromObjectName(name)
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
@@ -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 ---@class SupplyUnitsTracker : OnPlayerEnterUnitListener
---@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,6 +43,8 @@ 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()
@@ -134,6 +136,7 @@ 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
@@ -158,15 +161,17 @@ 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
if self._cargoInUnits[unitID] == nil then local unitIdStr = tostring(unitID)
self._cargoInUnits[unitID] = {}
if self._cargoInUnits[unitIdStr] == nil then
self._cargoInUnits[unitIdStr] = {}
end end
if self._cargoInUnits[unitID][crateType] == nil then if self._cargoInUnits[unitIdStr][crateType] == nil then
self._cargoInUnits[unitID][crateType] = 0 self._cargoInUnits[unitIdStr][crateType] = 0
end end
self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1 self._cargoInUnits[unitIdStr][crateType] = self._cargoInUnits[unitIdStr][crateType] + 1
end end
@@ -253,7 +258,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end end
end end
self._unitPositions[unit:getID()] = pos self._unitPositions[tostring(unit:getID())] = pos
end end
end end
end end
@@ -323,6 +328,7 @@ 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,
@@ -484,7 +490,7 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
end end
local objPos = foundObject:getPoint() local objPos = foundObject:getPoint()
local box = desc.box local box = desc.box --[[@type table]]
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
@@ -497,10 +503,10 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
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 local minX = box.min.x --[[@as number]]
local maxX = box.max.x local maxX = box.max.x --[[@as number]]
local minZ = box.min.z local minZ = box.min.z --[[@as number]]
local maxZ = box.max.z local maxZ = box.max.z --[[@as number]]
-- 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
@@ -517,8 +523,8 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
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) local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading) --[[@as number]]
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading) local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading) --[[@as number]]
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)
@@ -587,7 +593,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
return nil return nil
end end
local crateRelativeBBox = crateDesc.box local crateRelativeBBox = crateDesc.box --[[@as table]]
-- Get drop zone config for this unit -- Get drop zone config for this unit
local dropZones = { local dropZones = {
@@ -610,8 +616,11 @@ 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 = {} local occupiedObjects = {}
---@param foundItem Object
---@param _ any
local found = function(foundItem, _) local found = function(foundItem, _)
local bbox = self:GetBoundingBoxes(foundItem) local bbox = self:GetBoundingBoxes(foundItem)
if bbox then if bbox then
@@ -660,6 +669,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
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,6 +25,8 @@ 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 ---@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 .. "." ..
@@ -43,6 +45,7 @@ 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)
@@ -117,6 +120,7 @@ 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
@@ -143,17 +147,20 @@ 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)
if self._markIDsPerGroup[groupID] then local groupIdStr = tostring(groupID)
DcsUtil.RemoveMark(self._markIDsPerGroup[groupID])
end
if self._markIDsPerGroup[groupIdStr] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupIdStr])
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[groupID] = markID self._markIDsPerGroup[groupIdStr] = markID
end end
---@private ---@private
@@ -197,6 +204,7 @@ function BuildableMission:SpawnActive()
---@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()
@@ -251,7 +259,7 @@ function BuildableMission:CheckCratesInZone()
local crates = self._supplyUnitsTracker:GetCargoCratesDropped() local crates = self._supplyUnitsTracker:GetCargoCratesDropped()
for _, staticObject in pairs(crates) do for _, staticObject in pairs(crates) do
if staticObject and staticObject:isExist() and Util.startswith(staticObject:getName(), self._crateType, true) then if staticObject and staticObject:isExist() and Util.startsWith(staticObject:getName(), self._crateType, true) then
local pos = staticObject:getPoint() local pos = staticObject:getPoint()
if Util.is3dPointInZone(pos, self._dropOffZone) then if Util.is3dPointInZone(pos, self._dropOffZone) then
@@ -24,6 +24,7 @@ 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)
@@ -205,8 +206,9 @@ 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 any ---@param selfA RunwayStrikeMission
---@return unknown ---@param time number
---@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
@@ -215,6 +217,7 @@ function RunwayStrikeMission:StartRepair()
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
@@ -247,12 +250,14 @@ function RunwayStrikeMission:DoRepairCycle()
end end
---@class StaticSpawn ---@class StaticSpawn : table
---@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
@@ -441,6 +446,7 @@ 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)
@@ -14,7 +14,7 @@ local DcsUtil = require("classes.util.DcsUtil")
---@field private _completeAtIndex number ---@field private _completeAtIndex number
---@field private _parentStage Stage ---@field private _parentStage Stage
---@field private _battleManager? BattleManager ---@field private _battleManager? BattleManager
---@field private _lastContactMarkerID number; ---@field private _lastContactMarkerID number
local ZoneMission = {} local ZoneMission = {}
--- @class MissionGroups --- @class MissionGroups
@@ -24,7 +24,7 @@ local ZoneMission = {}
--- @field unitsAlive table<string, table<string, boolean>> --- @field unitsAlive table<string, table<string, boolean>>
--- @field targetsAlive table<string, table<string, boolean>> --- @field targetsAlive table<string, table<string, boolean>>
--- @field sceneryTargets Array<SpearheadSceneryObject> --- @field sceneryTargets Array<SpearheadSceneryObject>
--- @field groupNamesPerunit table<string,string> --- @field groupNamesPerUnit table<string,string>
---@class ParsedMissionName ---@class ParsedMissionName
---@field missionName string ---@field missionName string
@@ -36,7 +36,7 @@ local ZoneMission = {}
local function ParseZoneName(input) local function ParseZoneName(input)
local split_name = Util.split_string(input, "_") local split_name = Util.split_string(input, "_")
local split_length = Util.tableLength(split_name) local split_length = Util.tableLength(split_name)
if Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then if Util.startsWith(input, "RANDOMMISSION") == true and split_length < 4 then
MissionEditorWarnings.Add("Random Mission with zonename " .. input .. " not in right format") MissionEditorWarnings.Add("Random Mission with zonename " .. input .. " not in right format")
return nil return nil
elseif split_length < 3 then elseif split_length < 3 then
@@ -53,9 +53,10 @@ local function ParseZoneName(input)
if inputType == "cas" then parsedType = "CAS" end if inputType == "cas" then parsedType = "CAS" end
if inputType == "bai" then parsedType = "BAI" end if inputType == "bai" then parsedType = "BAI" end
if inputType == "sam" then parsedType = "SAM" end if inputType == "sam" then parsedType = "SAM" end
if inputType == "deepstrike" then parsedType = "DEEPSTRIKE" end
if parsedType == "nil" then if parsedType == "nil" then
MissionEditorWarnings.Add("Mission with zonename '" .. MissionEditorWarnings.Add("Mission with zoneName '" ..
input .. "' has an unsupported type '" .. (type or "nil")) input .. "' has an unsupported type '" .. (type or "nil"))
return nil return nil
end end
@@ -66,8 +67,6 @@ local function ParseZoneName(input)
} }
end end
MINIMAL_UNITS_ALIVE_RATIO = 0.21
---comment ---comment
---@param zoneName string ---@param zoneName string
---@param priority MissionPriority ---@param priority MissionPriority
@@ -112,7 +111,7 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
unitsAlive = {}, unitsAlive = {},
targetsAlive = {}, targetsAlive = {},
hasTargets = false, hasTargets = false,
groupNamesPerunit = {}, groupNamesPerUnit = {},
sceneryTargets = {} sceneryTargets = {}
} }
@@ -150,17 +149,17 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true) local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._missionGroups.redGroups, spearheadGroup) table.insert(self._missionGroups.redGroups, spearheadGroup)
local isGroupTarget = Util.startswith(string.lower(groupName), "tgt_") local isGroupTarget = Util.startsWith(string.lower(groupName), "tgt_")
for _, unit in pairs(spearheadGroup:GetObjects()) do for _, unit in pairs(spearheadGroup:GetObjects()) do
local unitName = unit:getName() local unitName = unit:getName()
local isUnitTarget = Util.startswith(string.lower(unitName), "tgt_") local isUnitTarget = Util.startsWith(string.lower(unitName), "tgt_")
if self._missionGroups.unitsAlive[groupName] == nil then if self._missionGroups.unitsAlive[groupName] == nil then
self._missionGroups.unitsAlive[groupName] = {} self._missionGroups.unitsAlive[groupName] = {}
end end
self._missionGroups.unitsAlive[groupName][unitName] = true self._missionGroups.unitsAlive[groupName][unitName] = true
self._missionGroups.groupNamesPerunit[unitName] = groupName self._missionGroups.groupNamesPerUnit[unitName] = groupName
if isGroupTarget == true or isUnitTarget == true then if isGroupTarget == true or isUnitTarget == true then
self._missionGroups.hasTargets = true self._missionGroups.hasTargets = true
@@ -180,7 +179,6 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
if self.missionType == "CAS" then if self.missionType == "CAS" then
self._battleManager = BattleManager.New(self._missionGroups.redGroups, self._missionGroups.blueGroups, self.zoneName, self._logger.LogLevel) self._battleManager = BattleManager.New(self._missionGroups.redGroups, self._missionGroups.blueGroups, self.zoneName, self._logger.LogLevel)
end end
self._logger:debug("Mission " .. self.name .. " group count: " .. Util.tableLength(missionData.RedGroups)) self._logger:debug("Mission " .. self.name .. " group count: " .. Util.tableLength(missionData.RedGroups))
@@ -229,10 +227,9 @@ end
---@internal ---@internal
---@param checkHealth boolean ---@param checkHealth boolean
---@param messageIfDone boolean ---@param _ boolean
function ZoneMission:UpdateState(checkHealth, messageIfDone) function ZoneMission:UpdateState(checkHealth, _)
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
@@ -240,7 +237,7 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
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 local life0 = staticObject:getDesc().life --[[@as number]]
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)
@@ -286,7 +283,9 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
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
@@ -299,13 +298,13 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
end end
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
total = total + 1 total = total + 1 --[[@as number]]
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 local deadRatio = (total - alive) / total --[[@as number]]
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"
@@ -354,6 +353,19 @@ end
function ZoneMission:SpawnInactive() function ZoneMission:SpawnInactive()
self._logger:info("PreActivating " .. self.name) self._logger:info("PreActivating " .. self.name)
if self.missionType == "DEEPSTRIKE" then
local missionData = self._database:getMissionDataForZone(self.zoneName)
---@type MissionPriority
local priority = "secondary"
if missionData and missionData.primaryOverwrite ~= nil then
if missionData.primaryOverwrite == true then
priority = "primary"
end
end
self._priority = priority
self._missionCommandsHelper:AddMissionToCommands(self)
end
for _, group in pairs(self._missionGroups.redGroups) do for _, group in pairs(self._missionGroups.redGroups) do
group:Spawn() group:Spawn()
end end
@@ -390,6 +402,17 @@ function ZoneMission:SpawnActive()
self._battleManager:Start() self._battleManager:Start()
end end
self._missionCommandsHelper:RemoveMissionToCommands(self) -- Remove first
if self.missionType == "DEEPSTRIKE" then
local missionData = self._database:getMissionDataForZone(self.zoneName)
local priority = "primary"
if missionData and missionData.primaryOverwrite ~= nil then
if missionData.primaryOverwrite == false then
priority = "secondary"
end
end
self._priority = priority
end
self._missionCommandsHelper:AddMissionToCommands(self) self._missionCommandsHelper:AddMissionToCommands(self)
self:StartCheckingContinuous() self:StartCheckingContinuous()
@@ -477,16 +500,18 @@ 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)
local groupName = self._missionGroups.groupNamesPerunit[unitName] local groupName = self._missionGroups.groupNamesPerUnit[unitName]
self._missionGroups.unitsAlive[groupName][unitName] = false self._missionGroups.unitsAlive[groupName][unitName] = false
if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then
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
@@ -9,7 +9,7 @@ local GlobalConfig = require("classes.configuration.GlobalConfig")
---@field zoneName string ---@field zoneName string
---@field missionType MissionType ---@field missionType MissionType
---@field missionTypeDisplay string ---@field missionTypeDisplay string
---@field priority MissionPriority ---@field protected _priority MissionPriority
---@field location Vec2? ---@field location Vec2?
---@field code string ---@field code string
---@field protected _state MissionState ---@field protected _state MissionState
@@ -40,7 +40,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.zoneName = zoneName self.zoneName = zoneName
self.name = missionName self.name = missionName
self.missionType = missionType self.missionType = missionType
self.priority = priority self._priority = priority
self._state = "NEW" self._state = "NEW"
self._logger = logger self._logger = logger
self._database = database self._database = database
@@ -64,14 +64,19 @@ end
--region PUBLIC --region PUBLIC
---@return MissionPriority
function Mission:GetPriority()
return self._priority
end
function Mission:SpawnPersistedState() end 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 ---@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()
@@ -86,6 +91,7 @@ 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()
@@ -113,7 +119,7 @@ end
function Mission:NotifyMissionComplete() function Mission:NotifyMissionComplete()
self._missionCommandsHelper:RemoveMissionToCommands(self) self._missionCommandsHelper:RemoveMissionToCommands(self)
self._logger:info("Mission Completed: " .. self.zoneName) self._logger:info("Mission Completed: " .. self.zoneName)
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed succesfully", 20) trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed successfully", 20)
for _, listener in pairs(self._completeListeners) do for _, listener in pairs(self._completeListeners) do
pcall(function() pcall(function()
@@ -132,9 +138,9 @@ function Mission:ForceMissionComplete()
self:NotifyMissionComplete() self:NotifyMissionComplete()
end end
---@param groupId number ---@param _groupId number
---@diagnostic disable-next-line: unused-local ---@diagnostic disable-next-line: unused-local
function Mission:MarkMissionAreaToGroup(groupId) end function Mission:MarkMissionAreaToGroup(_groupId) end
---endregion ---endregion
@@ -146,8 +152,6 @@ function Mission:ToStateString() return "status: in progress" end
--endregion --endregion
do --aliases
--- @alias MissionPriority --- @alias MissionPriority
--- | "none" --- | "none"
--- | "primary" --- | "primary"
@@ -156,19 +160,19 @@ do --aliases
--- @alias MissionType --- @alias MissionType
--- | "nil" --- | "nil"
--- | "STRIKE" --- | "STRIKE"
--- | "DEEPSTRIKE"
--- | "CAS" --- | "CAS"
--- | "BAI" --- | "BAI"
--- | "DEAD" --- | "DEAD"
--- | "SAM" --- | "SAM"
--- | "OCA" --- | "OCA"
--- | "LOGISTICS" --- | "LOGISTICS"
---
--- @alias MissionState --- @alias MissionState
--- | "NEW" --- | "NEW"
--- | "WAITING" --- | "WAITING"
--- | "ACTIVE" --- | "ACTIVE"
--- | "COMPLETED" --- | "COMPLETED"
end
return Mission return Mission
+57 -21
View File
@@ -32,6 +32,10 @@ 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
@@ -40,7 +44,7 @@ do -- INIT DCS_UTIL
---@field zone_type SpearheadTriggerZoneType ---@field zone_type SpearheadTriggerZoneType
---@field properties Array<KeyValuePair>? ---@field properties Array<KeyValuePair>?
---@type Array<SpearheadTriggerZone> ---@type table<string, SpearheadTriggerZone>
DCS_UTIL.__trigger_zones = {} DCS_UTIL.__trigger_zones = {}
end end
@@ -66,12 +70,15 @@ 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
@@ -104,8 +111,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
@@ -116,6 +123,7 @@ 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"
@@ -126,12 +134,12 @@ do -- INIT DCS_UTIL
end end
if env.warehouses.warehouses then if env.warehouses.warehouses then
DCS_UTIL.__warehouseStartingCoalition[-1] = "placeholder" env.warehouses.warehouses = env.warehouses.warehouses --[[@as table<number, any>]]
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
warehouse_id = tostring(warehouse_id) or "nil" local warehouse_id_str = tostring(warehouse_id) or "nil"
local coalitionNumber = DCS_UTIL.stringToCoalition(value.coalition) local coalitionNumber = DCS_UTIL.stringToCoalition(value.coalition)
DCS_UTIL.__warehouseStartingCoalition[warehouse_id] = coalitionNumber DCS_UTIL.__warehouseStartingCoalition[warehouse_id_str] = coalitionNumber
end end
end end
end end
@@ -218,11 +226,13 @@ 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 table unit names ---@param unit_names Array<string> unit names
---@param zone_names table zone names ---@param zone_names Array<string> zone names
---@return table unit list of objects { unit = UNIT, zone_name = zoneName} ---@return table<number, { unit : Unit|StaticObject, zone_name: string }>
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>
@@ -241,14 +251,19 @@ do -- INIT DCS_UTIL
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 unit_pos = lUnit:getPosition().p local isActive = true
local lCat = Object.getCategory(lUnit) 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
for _, zone in pairs(zones) do for _, 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 if unit_pos and isActive == true 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 }
@@ -262,9 +277,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 table groupnames list of group names ---@return Array<string> 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] local zone = DCS_UTIL.__trigger_zones[zone_name] --[[@as SpearheadTriggerZone?]]
if zone == nil then if zone == nil then
return {} return {}
end end
@@ -277,11 +292,13 @@ 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
local entry = nil ---@type { unit : Unit|StaticObject|nil, groupname: string }|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] }
@@ -290,7 +307,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] = entry units[#units + 1] = { groupname = entry.groupname, unit = entry.unit }
end end
end end
@@ -308,8 +325,8 @@ 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 table zone names ---@param zone_names Array<string> zone names
---@return table zones list of objects { zone_name = zoneName} ---@return Array<string> 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 = {}
@@ -319,7 +336,7 @@ do -- INIT DCS_UTIL
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 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
@@ -396,6 +413,18 @@ 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 =
{ {
@@ -408,7 +437,8 @@ 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)
@@ -429,6 +459,7 @@ 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)
@@ -440,7 +471,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 local minutes = (math.abs(dd) - degrees) * 60 --[[@as number]]
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
@@ -559,6 +590,7 @@ 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
@@ -571,6 +603,7 @@ 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)
@@ -616,6 +649,7 @@ 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 unitName = "dead_" .. unitName
local object = StaticObject.getByName(unitName) local object = StaticObject.getByName(unitName)
@@ -734,6 +768,8 @@ 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
+7
View File
@@ -3,6 +3,13 @@ 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"
+7 -3
View File
@@ -1,8 +1,12 @@
---@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, warningMessage or "skip") table.insert(MissionEditingWarnings.warnings, warningMessage or "skip")
end end
---@param logger Logger ---@param logger Logger
@@ -12,12 +16,12 @@ function MissionEditingWarnings.WriteAll(logger)
return return
end end
if not MissionEditingWarnings or #MissionEditingWarnings == 0 then if not MissionEditingWarnings.warnings or #MissionEditingWarnings.warnings == 0 then
return return
end end
logger:warn("Mission Editor Warnings:") logger:warn("Mission Editor Warnings:")
for _, warning in ipairs(MissionEditingWarnings) do for _, warning in ipairs(MissionEditingWarnings.warnings) do
logger:warn("- " .. warning) logger:warn("- " .. warning)
end end
+45 -22
View File
@@ -1,13 +1,13 @@
---@class Util ---@class Util
local UTIL = {} local UTIL = {}
do -- INIT UTIL do -- INIT UTIL
---splits a string in sub parts by seperator ---splits a string in sub parts by separator
---@param input string ---@param input string
---@param seperator string ---@param separator string
---@return table result list of strings ---@return Array<string> result list of strings
function UTIL.split_string(input, seperator) function UTIL.split_string(input, separator)
if seperator == nil then if separator == nil then
seperator = " " separator = " "
end end
local result = {} local result = {}
@@ -15,14 +15,14 @@ do -- INIT UTIL
return result return result
end end
for str in string.gmatch(input, "[^" .. seperator .. "]+") do for str in string.gmatch(input, "[^" .. separator .. "]+") do
table.insert(result, str) table.insert(result, str)
end end
return result return result
end end
---comment ---comment
---@param table any ---@param table table<any, 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,23 +35,34 @@ 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
---@return any
local function deepCopy(original)
local orig_type = type(original)
---@type any
local copy local copy
if orig_type == 'table' then if orig_type == 'table' then
copy = {} original = original --[[@as table<any, any>]]
for orig_key, orig_value in next, orig, nil do copy = {} --[[@as table<any, any>]]
copy[UTIL.deepCopyTable(orig_key)] = UTIL.deepCopyTable(orig_value) for orig_key, orig_value in pairs(original) do
local copiedKey = deepCopy(orig_key) --[[@as any]]
copy[copiedKey] = deepCopy(orig_value)
end end
setmetatable(copy, UTIL.deepCopyTable(getmetatable(orig))) setmetatable(copy, deepCopy(getmetatable(original)))
else -- number, string, boolean, etc else -- number, string, boolean, etc
copy = orig copy = original --[[@as any]]
end end
return copy return copy
end end
return deepCopy(orig)
end
---Gets a random from the list ---Gets a random from the list
---@param list Array ---@generic T
---@return any @random element from the list ---@param list Array<T>
---@return T @random element from the list
function UTIL.randomFromList(list) function UTIL.randomFromList(list)
local max = #list local max = #list
@@ -65,11 +76,13 @@ do -- INIT UTIL
return list[random] return list[random]
end end
---@param list Array ---@generic T
---@param list Array<T>
---@param start number start ---@param start number start
---@param n number length ---@param n number length
---@return Array ---@return Array<T>
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]
@@ -90,9 +103,10 @@ do -- INIT UTIL
end end
local function table_print(tt, indent, done) local function table_print(tt, indent, done)
done = done or {} done = done or {} --[[@as table<any, boolean>]]
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
@@ -120,7 +134,7 @@ do -- INIT UTIL
---@param findable string ---@param findable string
---@param ignoreCase boolean? ---@param ignoreCase boolean?
---@return boolean ---@return boolean
UTIL.startswith = function(str, findable, ignoreCase) UTIL.startsWith = function(str, findable, ignoreCase)
if ignoreCase == true then if ignoreCase == true then
return string.lower(str):find('^' .. string.lower(findable)) ~= nil return string.lower(str):find('^' .. string.lower(findable)) ~= nil
end end
@@ -146,11 +160,11 @@ do -- INIT UTIL
---comment ---comment
---@param str string ---@param str string
---@param findableTable table ---@param findableTable table<any,any>
---@return boolean ---@return boolean
UTIL.startswithAny = function(str, findableTable) UTIL.startswithAny = function(str, findableTable)
for _, value in pairs(findableTable) do for _, 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
end end
@@ -244,6 +258,7 @@ do -- INIT UTIL
---@return boolean ---@return boolean
local function isInComplexPolygon(polygon, x, y) local function isInComplexPolygon(polygon, x, y)
---@param poly Array<Vec2> of pairs { 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
@@ -280,6 +295,7 @@ 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
@@ -296,6 +312,7 @@ 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
@@ -447,6 +464,8 @@ 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 {}
@@ -524,6 +543,10 @@ 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)