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 9b2540d461 Fixed an issue
Publish Release / build (push) Successful in 30s
2026-09-09 22:06:29 +02: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
52 changed files with 781 additions and 385 deletions
+1 -1
View File
@@ -169,7 +169,7 @@ class Sidebar extends HTMLElement {
render() {
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>';
}
+106 -30
View File
@@ -26,42 +26,118 @@
<div class="content-wrapper">
<h1>Advanced Tutorial: Missions</h1>
<h2 id="strike">Strike</h2>
<p>
Naming: <span class="inline-lua"><span class="lua-variable">MISSION_STRIKE_[Name]</span></span> <br>
</p>
<h3 id="specific_targets">Specific Targets</h3>
<p>TBD</p>
<div id="strike-region">
<h2 id="strike">Strike</h2>
<p>
Naming: <code-inline>MISSION_STRIKE_[Name]</code-inline> <br>
</p>
<h3 id="specific_targets">Specific Targets</h3>
<p>TBD</p>
<h3 id="scenery_targets">Scenery Targets</h3>
<p>
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
We wanted to make sure they were easily integrated in Spearhead.
</p>
<h3 id="scenery_targets">Scenery Targets</h3>
<p>
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
We wanted to make sure they were easily integrated in Spearhead.
</p>
<note-box type="info" title="Implementation details">
Spearhead detects scenery targets by the trigger zone name. A zone named <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.
</note-box>
<p>
Scenery targets are treated like mission targets: they are included in the mission completion calculation
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or resume.
</p>
<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.
Hence the need to use trigger zones to define them.
<note-box type="info" title="Implementation details">
Spearhead detects scenery targets by the trigger zone name. A zone named
<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.
</note-box>
<p>Here's two images showing how a bridge is added to a mission.</p>
<img src="/img/bridge.png" style="max-height: 35remrem;"/>
<p>
Scenery targets are treated like mission targets: they are included in the mission completion
calculation
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or
resume.
</p>
<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.
Hence the need to use trigger zones to define them.
</note-box>
<p>Here's two images showing how a bridge is added to a mission.</p>
<img src="/img/bridge.png" style="max-height: 35remrem;" />
</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>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
+1 -1
View File
@@ -8,7 +8,7 @@
--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-text-primary: #a5a5a5; /* Brighter text */
--color-text-primary: #b4b3b3; /* Brighter text */
--color-text-secondary: #bfc8d8; /* Lighter secondary text */
--color-text-accent: #7ecbff; /* Brighter accent */
--color-text-light: #ffffff;
+74 -5
View File
@@ -24,7 +24,7 @@ jobs:
luaVersion: ${{ env.LUA_VERSION }}
- 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:
source-root: ./src
output-file: ./output/spearhead.lua
@@ -38,6 +38,39 @@ jobs:
- name: Install luacheck
run: luarocks install luacheck
# "lfs",
# "setmetatable", "getmetatable", "table", "string", "math", "pairs", "ipairs",
# "require", "type", "tostring", "io", "error", "pcall", "tonumber",
# "loadstring",
- name: Create LuaCheck Config
run: |
cat << EOF > .luacheckrc
stds.spearhead_lib = {
globals = {
"SpearheadConfig", "SpearheadAPI", "GlobalCapCallBacks",
}
}
stds.dcs_lib = {
globals = {
"env", "net", "trigger", "timer", "Object", "land", "coord", "coalition",
"Unit", "Group", "Airbase", "StaticObject", "SceneryObject", "world",
"country", "missionCommands", "Controller", "Weapon", "atmosphere", "country",
"lfs", "AI"
}
}
std = "lua51c+spearhead_lib+dcs_lib"
self = false
max_line_length = false
EOF
- name: Show LuaCheck Config
run: cat .luacheckrc
- name: Lint Compiled Output
run: luacheck ./output/spearhead.lua
@@ -53,6 +86,14 @@ jobs:
with:
luaVersion: ${{ env.LUA_VERSION }}
- name: Install Dcs Plugins
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/install-lua-addon@install-lua-addon/v1
with:
destination-path: "./.vscode/lua-addons" ##same as local so .luarc.json can be the same
- name: Verify Dcs Plugins Installation
run: ls -la ./.vscode/lua-addons
- name: Install lua-language-server
run: |
mkdir -p ./luals
@@ -64,8 +105,36 @@ jobs:
- name: Verify Lua Language Server Installation
run: ./luals/bin/lua-language-server --version
- name: Run Lua Language Server
run: ./luals/bin/lua-language-server --check ./src --checklevel=Warning
- name: Update Json
run: |
FULL_PATH=$(realpath ./.vscode/lua-addons)
echo "full-path: $FULL_PATH"
ls -R $FULL_PATH
echo "Updating .luarc.json with full path"
jq --arg full_path "$FULL_PATH" '.workspace.library[0] = $full_path' ./.luarc.json > ./.luarc.tmp.json
mv ./.luarc.tmp.json ./.luarc.json
- name: Show Config
run: cat ./.luarc.json
- name: Run Lua Language Server
run: |
CONFIG_FULL_PATH=$(realpath ./.luarc.json)
echo "Config full path: $CONFIG_FULL_PATH"
./luals/bin/lua-language-server \
--check ./src \
--checklevel=Warning \
--loglevel=trace \
--logpath="./luals/logs" \
--configpath="$CONFIG_FULL_PATH" \
# --develop
- name: Show Log
if: failure()
run: |
ls -R ./luals/logs
cat ./luals/logs/* || true
+2 -2
View File
@@ -1,4 +1,4 @@
name: Publish Release
name: Publish Beta Release
on:
push:
@@ -46,7 +46,7 @@ jobs:
echo "EOF" >> "$GITHUB_OUTPUT"
- 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:
source-root: ./src
output-file: ./output/spearhead.lua
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
echo "EOF" >> "$GITHUB_OUTPUT"
- 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:
source-root: ./src
output-file: ./output/spearhead.lua
+1
View File
@@ -3,5 +3,6 @@
/dist
.vscode/lua-addons
.vscode/settings.json
**\settings.json
+46 -25
View File
@@ -1,30 +1,51 @@
{
"Lua": {
"runtime": {
"version": "Lua 5.1"
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "Lua 5.1"
},
"workspace": {
"checkThirdParty": false,
"maxPreload": 1600,
"preloadFileSize": 1000,
"ignoreDir": [ "config.lua" ]
},
"diagnostics": {
"enable": true,
"globals": [
"lfs"
],
"groupSeverity": {
"duplicate": "Warning",
"global":"Warning",
"luadoc": "Warning",
"redefined": "Warning",
"strong": "Warning",
"type-check": "Warning",
"unbalanced" : "Warning",
"ambiguity": "Warning",
"strict": "Warning",
"unused": "Warning"
},
"workspace": {
"library": [
"./src",
"./DcsTypes.lua"
],
"checkThirdParty": false
"groupFileStatus": {
"ambiguity": "Any",
"await": "Any",
"duplicate": "Any",
"global": "Any",
"luadoc": "Any",
"redefined": "Any",
"strict": "Any",
"type-check": "Any",
"unbalanced": "Any",
"unused": "Any",
"strong": "Any"
},
"diagnostics": {
"enable": true,
"globals": [
"lfs"
],
"severity": {
"undefined-field": "Warning",
"unused-local": "Warning"
}
},
"hint": {
"enable": false
},
"completion": {
"enable": true
}
"ignoredFiles": "Opened",
"libraryFiles": "Opened"
},
"hint": {
"enable": false
},
"completion": {
"enable": true
}
}
+5 -4
View File
@@ -1,5 +1,6 @@
{
"recommendations": [
"sumneko.lua"
]
{
"recommendations": [
"dutchie031.dutchies-dcs-scripting-tools",
"sumneko.lua",
]
}
+5
View File
@@ -19,6 +19,11 @@
Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
#44
- Issue: #42
DEEPSTRIKE now added as a possible feature.
This enables to have missions active in "Pre-Activated" Stages.
#48
### Bug Fixes
## [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
local GlobalCapManager = {}
do
---@type table<string, CapBase[]>
local airbasesPerStage = {}
---@type table<string, CapBase>
local allAirbasesByName = {}
local initiated = false
@@ -5,7 +5,7 @@ local GlobalConfig = require("classes.configuration.GlobalConfig")
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
---@class AirGroup : OnUnitLostListener
---@class AirGroup : OnUnitLostListener, OnGroupRTBListener, OnGroupRTBInTenListener, OnLandEventListener, OnGroupOnStationListener
---@field protected _logger Logger
---@field protected _groupName string
---@field protected _groupType AirGroupType
@@ -107,8 +107,9 @@ function AirGroup:SetMissionPrivate(mission)
local points = {}
if mission and mission.params and mission.params.route and mission.params.route.points then
for _, wp in pairs(mission.params.route.points) do
if wp.x and wp.y then
local routePoints = mission.params.route.points --[[@as Array<Vec2> ]]
for _, wp in pairs(routePoints) do
if wp and wp.x and wp.y then
table.insert(points, { x = wp.x, y = wp.y })
end
end
@@ -198,6 +199,8 @@ function AirGroup:SpawnInternal(force, withoutLoadout)
end
---@param selfA AirGroup
---@param time number
---@return number?
local function CheckLivenessTask(selfA, time)
local interval = selfA:CheckLiveness()
if not interval then return end
@@ -263,6 +266,7 @@ function AirGroup:OnLastUnitLanded()
---@param data CheckGroupForRestartData
---@param time number
---@return number?
local checkGroupForRestart = function(data, time)
local group = Group.getByName(data.self:GetName())
if not group then
@@ -34,9 +34,10 @@ function CapGroup:IsBackup()
return self._isBackup
end
---@param stageID string
---@return string?
function CapGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[stageID]
return self._targetZoneIdPerStage[tostring(stageID)]
end
---@return string?
@@ -60,6 +60,7 @@ end
---comment
---@param units Array<string>
---@param zoneName string
---@param homeAirbase Airbase
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
@@ -68,9 +69,10 @@ function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
self:SetTargetUnits(units)
end
---@param stageID string
---@return string?
function InterceptGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[stageID]
return self._targetZoneIdPerStage[tostring(stageID)]
end
---@return string?
@@ -91,6 +93,9 @@ function InterceptGroup:SetTargetUnits(unitNames)
end)
end
---@param selfA InterceptGroup
---@param time number
---@return number?
local updateContinous = function(selfA, time)
local next = selfA:UpdateTask()
if next then
@@ -102,6 +107,7 @@ function InterceptGroup:SetTargetUnits(unitNames)
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
end
---@param unit Unit
function InterceptGroup:RemoveTargetUnit(unit)
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
end
local mission = nil
---@type table
local mission
if self:IsInAir() == true then
-- If the group is in the air, create an intercept mission
mission = INTERCEPT.getMissionFromInAir(
@@ -4,7 +4,7 @@ local Util = require("classes.util.Util")
local MissionEditorWarner = require("classes.util.MissionEditorWarnings")
---@class SweepGroup : AirGroup
---@field _targetZoneIdPerStage table<number, string>
---@field _targetZoneIdPerStage table<string, string>
---@field _currentTargetZoneID string?
local SweepGroup = {}
SweepGroup.__index = SweepGroup
@@ -26,9 +26,10 @@ function SweepGroup.New(groupName, config, logger, spawnManager)
return self
end
---@param stageID string
---@return string?
function SweepGroup:GetZoneIDWhenStageID(stageID)
return self._targetZoneIdPerStage[stageID]
return self._targetZoneIdPerStage[tostring(stageID)]
end
---@return string?
@@ -8,6 +8,7 @@ DetectionManager.__index = DetectionManager
---@param logger Logger
---@return DetectionManager
function DetectionManager.New(logger)
local self = setmetatable({}, DetectionManager)
@@ -24,6 +25,7 @@ function DetectionManager.New(logger)
---@param selfA DetectionManager
---@param time number
---@return number?
local updateDetectingUnitsTask = function(selfA, time)
selfA:UpdateDetectingUnits()
return time + 120
@@ -32,6 +34,7 @@ function DetectionManager.New(logger)
---@param selfA DetectionManager
---@param time number
---@return number?
local updateDetected = function(selfA, time)
selfA:UpdateDetectedUnits()
return time + 10
@@ -43,6 +46,7 @@ end
---@param unitName string
---@param coalitionSide CoalitionSide
---@return boolean
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
local coalitionString = tostring(coalitionSide)
if not self._detectedUnits[coalitionString] then
@@ -56,7 +60,8 @@ function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
end
---@return Array<string>
---@param coalitionSide CoalitionSide
---@return Array<string> detectedUnits
function DetectionManager:GetDetectedUnitsBy(coalitionSide)
local coalitionString = tostring(coalitionSide)
if not self._detectedUnits[coalitionString] then
@@ -56,7 +56,9 @@ end
---@field self RunwayBombingTracker
---@private
---@param time number
---@param weaponTrackingArgs WeaponTrackingArgs
---@return number?
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
local weapon = weaponTrackingArgs.weapon
@@ -102,11 +104,10 @@ function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
local warhead = weaponDesc.warhead
local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass
local warhead = weaponDesc.warhead --[[@as table]]
local explosiveMass = (warhead.explosiveMass or warhead.shapedExplosiveMass) --[[@as number]]
for _, strikeMission in pairs(self.trackedRunways) do
local zone= strikeMission:GetRunwayZone()
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
---@return table
local function GetCAPTargetTypes(attackHelos)
---@type Array<string>
local targetTypes = {
[1] = "Planes",
}
@@ -53,7 +54,10 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
---@type Vec2
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
---@type Vec2
local pointA = capZone.verts[furthestA]
---@type Vec2
local pointB = capZone.verts[furthestB]
local furthest = pointA
local closest = pointB
@@ -84,6 +88,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
local GetOutboundTask = function(airbase, capZone, capConfig)
local airbaseVec3 = airbase:getPoint()
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
@@ -125,6 +130,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
local points = {
[1] = GetOutboundTask(airbase, capZone, capConfig),
@@ -152,6 +158,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
function CAP.getAsMission(groupName, airbase, capZone, capConfig)
local points = {
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
@@ -178,6 +185,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
@@ -11,6 +11,7 @@ local INTERCEPT = {}
---@param speed number?
---@param alt number?
---@param config CapConfig
---@return table
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
local airbaseVec3 = airbase:getPoint()
@@ -92,7 +93,7 @@ end
---@param currentPoint Vec2
---@param targetPosition Vec2
---@param targetUnit Unit
---@param airbase any
---@param airbase Airbase
---@param config any
---@param speed number?
---@param alt number?
+5
View File
@@ -7,6 +7,7 @@ local RTB = {}
---@param airbase Airbase
---@param missionPoint Vec2
---@param capConfig CapConfig
---@return table
function RTB.getAsMission(airbase, missionPoint, capConfig)
return {
id = "Mission",
@@ -113,6 +114,7 @@ end
---comment
---@param airbase Airbase
---@param missionPoint Vec2
---@return table
---@param capConfig CapConfig
function RTB.getApproachPoint(airbase, missionPoint, capConfig)
@@ -147,6 +149,7 @@ function RTB.getApproachPoint(airbase, missionPoint, capConfig)
end
---@param airbase Airbase
---@return table
function RTB.getInitialPoint(airbase)
local point = calcInitialPoint(airbase)
return {
@@ -170,6 +173,8 @@ function RTB.getInitialPoint(airbase)
}
end
---@param airbase Airbase
---@return table
function RTB.getLandingPoint(airbase)
local basePoint = airbase:getPoint()
return {
@@ -7,6 +7,7 @@ local SWEEP = {}
---@param attackHelos boolean
---@return table
local function GetCAPTargetTypes(attackHelos)
---@type Array<string>
local targetTypes = {
[1] = "Planes",
}
@@ -49,7 +50,9 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
---@type Vec2
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
---@type Vec2
local pointA = capZone.verts[furthestA]
---@type Vec2
local pointB = capZone.verts[furthestB]
local furthest = pointA
local closest = pointB
@@ -80,6 +83,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
local GetOutboundTask = function(airbase, capZone, capConfig)
local airbaseVec3 = airbase:getPoint()
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
@@ -121,6 +125,7 @@ end
---@param airbase Airbase
---@param capZone SpearheadTriggerZone
---@param capConfig CapConfig
---@return table
function SWEEP.getAsMissionFromAirbase(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.CapConfig == nil then SpearheadConfig.CapConfig = {} end
if type(SpearheadConfig.CapConfig.enabled) ~= "boolean" then
SpearheadConfig.CapConfig.enabled = true
end
local enabled = SpearheadConfig.CapConfig.enabled
if enabled == nil then enabled = true end
self._isEnabled = enabled
+1 -1
View File
@@ -21,7 +21,7 @@ function DebugMenu:RegisterMenus()
missionCommands.addSubMenu(menuName, {})
local refresh = function(params)
local selfA = params.self
local selfA = params.self --[[@as DebugMenu]]
selfA:RefreshMenu()
end
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 targetZonePerStage table<string, 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 = {}
FleetGroup.__index = FleetGroup
@@ -78,7 +78,7 @@ function FleetGroup.new(fleetGroupName, database, logger)
return nil, nil
end
if Util.startswith(namePart, "%[") == true then
if Util.startsWith(namePart, "%[") == true then
namePart = Util.split_string(namePart, "[")[1]
end
@@ -108,7 +108,13 @@ function FleetGroup.new(fleetGroupName, database, logger)
for i = first, second do
self.targetZonePerStage[tostring(i)] = zoneName
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
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
end
@@ -125,10 +131,10 @@ function FleetGroup.new(fleetGroupName, database, logger)
end
local SetTaskAsync = function(input, _)
local targetZone = input.targetZone
local task = input.task
local groupName = input.groupName
local l_logger = input.logger
local targetZone = input.targetZone --[[@as string]]
local task = input.task --[[@as table]]
local groupName = input.groupName --[[@as string]]
local l_logger = input.logger --[[@as Logger]]
local group = Group.getByName(groupName)
if group then
@@ -15,7 +15,7 @@ GlobalFleetManager.start = function(database)
local all_groups = MizGroupsManager.getAllGroupNames()
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")
local carrierGroup = FleetGroup.new(groupName, database, logger)
table.insert(fleetGroups, carrierGroup)
+30 -7
View File
@@ -15,23 +15,45 @@ MizGroupsManager._groupNames = {}
MizGroupsManager._spawnTemplateData = {}
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
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)
if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then
for _, group in pairs(categorydata.group) do
local name = group.name
local groups_data = getGroupsData(country_data, category_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 isStatic = false
if category_id == DcsUtil.GroupCategory.STATIC then
isStatic = true
local unit = group.units[1]
local unit = group.units[1] --[[@as table]]
if unit and unit.category == "Heliports" then
skippable = true
elseif unit and unit.name then
name = unit.name
name = unit.name --[[@as string]]
else
env.error("Group " .. name .. " has no units, skipping it.")
skippable = true
@@ -71,6 +93,7 @@ function MizGroupsManager.IsGroupStatic(groupName)
return nil
end
---@param groupName string
---@return SpawnData?
function MizGroupsManager.getSpawnTemplateData(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
---@param logger Logger
---@return SpawnManager
function SpawnManager.new(logger)
local self = setmetatable({}, SpawnManager)
self._logger = logger
self._persistedUnits = {} -- Stores units that should be persisted by name
return self
@@ -125,37 +125,31 @@ do --- privates
end
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.
ONLY when perstable data is found.
]]
if spawnTemplate and spawnTemplate["units"] then
for _, unit in pairs(spawnTemplate["units"]) do
local name = unit["name"]
local units = spawnTemplate["units"] --[[@as Array<table>]]
for _, unit in pairs(units) do
local name = unit["name"] --[[@as string]]
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 unit["payload"] and unit["payload"]["pylons"] then
unit["payload"]["pylons"] = {}
local payload = unit["payload"] --[[@as table]]
payload["pylons"] = {} --[[@as table]]
end
end
if unit["parking"] then
unit["parking_landing"] = unit["parking"]
unit["parking_landing"] = unit["parking"] --[[@as number]]
end
if unit["parking_id"] then
unit["parking_landing_id"] = unit["parking_id"]
unit["parking_landing_id"] = unit["parking_id"] --[[@as number]]
end
end
@@ -169,7 +163,6 @@ do --- privates
local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
for _, unit in pairs(group:getUnits()) do
self:CheckUnitAndReplaceIfPersistentDead(unit)
if isPersistent == true then
@@ -210,9 +203,11 @@ do --- privates
spawnTemplate["x"] = persistentState.pos.x
spawnTemplate["y"] = persistentState.pos.z
if spawnTemplate["units"] and spawnTemplate["units"][1] then
spawnTemplate["units"][1]["x"] = persistentState.pos.x
spawnTemplate["units"][1]["y"] = persistentState.pos.z
spawnTemplate["units"][1]["heading"] = persistentState.heading or 0
local units = spawnTemplate["units"] --[[@as Array<table>]]
local firstUnit = units[1] --[[@as table]]
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
+17 -5
View File
@@ -141,14 +141,16 @@ do
end
---@param dir string @BaseDirectory
---@return string
---@param startsWith string
---@param default string?
---@return string?
local getLastFileOrDefault = function(dir, startsWith, default)
local latestFile, lastNumber = default, 0
for file in lfs.dir(dir) do
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
local numberString = split[#split-1]
local number = tonumber(numberString)
@@ -181,11 +183,14 @@ do
local fileName = "Spearhead_Persistence.0.spearhead"
if SpearheadConfig and SpearheadConfig.Persistence 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 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] = "spearhead"
end
@@ -198,7 +203,10 @@ do
fileName = table.concat(split, ".")
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
end
end
@@ -208,6 +216,10 @@ do
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
if lastFile == nil then
lastFile = fileName
end
local fileSplit = Util.split_string(lastFile, ".")
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
fileName = table.concat(fileSplit, ".")
+47 -23
View File
@@ -69,6 +69,7 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@field descriptionLocation Vec2?
---@field dependsOn Array<string>
---@field completeAt number?
---@field primaryOverwrite boolean?
---@class FarpZoneData
---@field groups Array<string>
@@ -211,12 +212,15 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
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)
if airbaseData then
self._logger:debug("found airbase data for " .. layer_object.name)
local number = tonumber(layer_object.text)
airbaseData.buildingKilos = number
if layer_object.primitiveType == "TextBox" then
layer_object = layer_object --[[@as TextBox]]
local number = tonumber(layer_object.text)
airbaseData.buildingKilos = number
end
end
end
end
@@ -231,7 +235,7 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
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 stageDrawing = StageDrawing.New(object)
if stageDrawing then
@@ -259,13 +263,16 @@ function Database.New(Logger)
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
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 vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if zone and Util.is2dPointInZone(vec2, zone) == true then
local description = layer_object.text
if description and description ~= "" then
stageData.StageBriefing = description
if layer_object.primitiveType == "TextBox" then
layer_object = layer_object --[[@as TextBox]]
local description = layer_object.text
if description and description ~= "" then
stageData.StageBriefing = description
end
end
end
end
@@ -468,6 +475,7 @@ function Database.New(Logger)
return self
end
---@type table<string, boolean>
local is_group_taken = {}
local getAvailableGroups = function()
@@ -484,7 +492,7 @@ end
local getAvailableCAPGroups = function()
local result = {}
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)
end
end
@@ -587,11 +595,11 @@ function Database:loadCapUnits()
for _, groupName in pairs(groups) do
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)
elseif Util.startswith(groupName, "CAP_I", true) then
elseif Util.startsWith(groupName, "CAP_I", true) then
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)
end
end
@@ -614,7 +622,7 @@ function Database:loadBlueSamUnits()
local triggerZone = DcsUtil.getZoneByName(blueSamZone)
if triggerZone then
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)
if number and number > 0 then
samData.buildingKilos = number
@@ -625,8 +633,9 @@ function Database:loadBlueSamUnits()
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) 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]]
local description = layer_object.text
if description and description ~= "" then
samData.briefing = description
@@ -702,9 +711,9 @@ function Database:LoadZoneData(missionZoneName)
if triggerZone and triggerZone.properties then
for _, kvPair in pairs(triggerZone.properties) do
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)
elseif Util.startswith(key, "completeat") == true then
elseif Util.startsWith(key, "completeat") == true then
local value = tonumber(kvPair.value)
if value then
if value > 1 and value <= 100 then
@@ -714,6 +723,14 @@ function Database:LoadZoneData(missionZoneName)
end
self._tables.MissionZoneData[missionZoneName].completeAt = value
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
@@ -725,8 +742,9 @@ function Database:LoadZoneData(missionZoneName)
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "briefing_", true) 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
layer_object = layer_object --[[@as TextBox]]
local description = layer_object.text
if description and description ~= "" then
self._tables.MissionZoneData[missionZoneName].description = description
@@ -769,7 +787,7 @@ function Database:loadFarpData()
local triggerZone = DcsUtil.getZoneByName(farpZone)
if triggerZone then
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)
if number and number > 0 then
farpzoneData.buildingKilos = number
@@ -781,8 +799,9 @@ function Database:loadFarpData()
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
layer_object = layer_object --[[@as TextBox]]
if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text
if description and description ~= "" then
farpzoneData.briefing = description
@@ -950,6 +969,7 @@ function Database:getBriefingForStage(stagename)
return stageZone.StageBriefing or ""
end
---@param stagename string
---@return Array<string>
function Database:getMissionsForStage(stagename)
local stageZone = self._tables.StageZones[stagename]
@@ -957,6 +977,7 @@ function Database:getMissionsForStage(stagename)
return stageZone.MissionZones
end
---@param stagename string
---@return Array<string>
function Database:getRandomMissionsForStage(stagename)
local stageZone = self._tables.StageZones[stagename]
@@ -964,13 +985,14 @@ function Database:getRandomMissionsForStage(stagename)
return stageZone.RandomMissionZones
end
---@param missionZoneName string
---@return MissionZoneData?
function Database:getMissionDataForZone(missionZoneName)
return self._tables.MissionZoneData[missionZoneName]
end
---@param stageName string
---@return table result airbase Names
---@return Array<string> result airbase Names
function Database:getAirbaseNamesInStage(stageName)
local stageData = self._tables.StageZones[stageName]
if not stageData then return {} end
@@ -984,6 +1006,7 @@ function Database:getFarpNamesInStage(stageName)
return stageData.FarpZones or {}
end
---@param farpZoneName string
---@return FarpZoneData?
function Database:getFarpDataForZone(farpZoneName)
local farpData = self._tables.FarpZoneData[farpZoneName]
@@ -999,6 +1022,7 @@ function Database:getAirbaseDataForZone(baseName)
return baseData
end
---@param stageName string
---@return string?
function Database:getStageBriefingForStage(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?)
do -- STAGE NUMBER CHANGED
---@type Array<OnStageChangedListener>
local OnStageNumberChangedListeners = {}
---@type Array<fun(number:integer, laneIdentifier:string?)>
local OnStageNumberChangedHandlers = {}
---Add a stage zone number changed listener
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier)
@@ -47,6 +49,7 @@ do
---@class OnStageNumberChangeCompleteListener
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
---@type Array<OnStageNumberChangeCompleteListener>
local OnStageNumberChangeCompleteListeners = {}
---@param listener OnStageNumberChangeCompleteListener
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
@@ -110,6 +113,9 @@ do
table.insert(onWeaponFiredListeners, weaponFiredListener)
end
---@param unit Unit
---@param weapon Weapon
---@param target Unit?
local triggerWeaponFired = function(unit, weapon, target)
for _, callable in pairs(onWeaponFiredListeners) do
local _, err = pcall(function()
@@ -122,11 +128,14 @@ do
end
end
---@class OnLandEventListener
---@field OnUnitLanded fun(self:OnLandEventListener, initiatorUnit:Unit, airbase:Airbase)
---@type table<string,Array<OnLandEventListener>>
local onLandEventListeners = {}
---Add an event listener to a specific unit
---@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)
if type(landListener) ~= "table" then
warn("Event handler not of type table/object")
@@ -162,6 +171,11 @@ do
end
do -- ON RTB
---@class OnGroupRTBListener
---@field OnGroupRTB fun(self:OnGroupRTBListener, groupName:string)
---@type table<string,Array<OnGroupRTBListener>>
local OnGroupRTBListeners = {}
---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
@@ -197,11 +211,15 @@ do
end
end
---@class OnGroupRTBInTenListener
---@field OnGroupRTBInTen fun(self:OnGroupRTBInTenListener, groupName:string)
---@type table<string,Array<OnGroupRTBInTenListener>>
local OnGroupRTBInTenListeners = {}
---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
---@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)
if type(handlingObject) ~= "table" then
warn("Event handler not of type table/object")
@@ -234,10 +252,17 @@ do
end
do -- ON Station
---@class OnGroupOnStationListener
---@field OnGroupOnStation fun(self:OnGroupOnStationListener, groupName:string)
---@type table<string,Array<OnGroupOnStationListener>>
local OnGroupOnStationListeners = {}
---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
---@param groupName string the groupname to expect
---@param handlingObject OnGroupOnStationListener object with OnGroupOnStation(self, groupName)
SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject)
if type(handlingObject) ~= "table" then
warn("Event handler not of type table/object")
@@ -270,15 +295,18 @@ do
end
do -- PLAYER ENTER UNIT
---@class OnPlayerEnterUnitListener
---@field OnPlayerEntersUnit fun(self:OnPlayerEnterUnitListener, unit:Unit)
---@type Array<OnPlayerEnterUnitListener>
local playerEnterUnitListeners = {}
---comment
---@param listener table object with OnPlayerEntersUnit(self, unit)
---@param listener OnPlayerEnterUnitListener object with OnPlayerEntersUnit(self, unit)
SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener)
if type(listener) ~= "table" then
warn("Unit lost Event listener not of type table/object")
return
end
table.insert(playerEnterUnitListeners, listener)
end
@@ -315,8 +343,8 @@ do
local e = {}
function e:onEvent(event)
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
local unit = event.initiator
local airbase = event.place
local unit = event.initiator --[[@as Unit]]
local airbase = event.place --[[@as Airbase]]
if unit ~= nil then
local name = unit:getName()
if onLandEventListeners[name] then
@@ -336,7 +364,7 @@ do
event.id == world.event.S_EVENT_CRASH or
event.id == world.event.S_EVENT_EJECTION or
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
logDebug("Receiving death event from: " .. object:getName())
@@ -355,15 +383,11 @@ do
end
end
if event.id == world.event.S_EVENT_EJECTION then
end
if event.id == world.event.S_EVENT_SHOT then
local shooter = event.initiator
local weapon = event.weapon
local target = event.target
local shooter = event.initiator --[[@as Unit]]
local weapon = event.weapon --[[@as Weapon]]
local target = event.target --[[@as Unit?]]
triggerWeaponFired(shooter, weapon, target)
end
@@ -372,10 +396,14 @@ do
Persistence.UpdateNow()
end
---@type table<string, boolean>
local AI_GROUPS = {}
---@param unit Unit
---@param _ any
local function CheckAndTriggerSpawnAsync(unit, _)
---@param checkUnit Unit
---@return boolean, string?
local function isPlayer(checkUnit)
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
@@ -405,8 +433,6 @@ do
if isPlayer(unit) == true then
SpearheadEvents.TriggerPlayerEntersUnit(unit)
end
return nil
end
if event.id == world.event.S_EVENT_BIRTH then
+13 -5
View File
@@ -8,6 +8,7 @@ do --setup route util
---@param attackHelos boolean
---@return table
local function GetCAPTargetTypes(attackHelos)
---@type table<number, string>
local targetTypes = {
[1] = "Planes",
}
@@ -53,8 +54,9 @@ do --setup route util
---comment
---@param groupName string
---@param position table { x, y}
---@param position Vec3
---@param altitude number
---@param deviationdistance number
---@param speed number
---@param duration number
---@param engageHelos boolean
@@ -181,7 +183,7 @@ do --setup route util
end
---comment
---@param position table { x, y}
---@param position Vec3
---@param altitude number
---@param speed number
---@param childTasks table
@@ -210,7 +212,8 @@ do --setup route util
---comment
---@param groupName string groupName you're creating this route for
---@param airdromeId number airdromeId
---@param capPoint table { x, z }
---@param capPoint Vec3
---@param racetrackSecondPoint Vec3?
---@param altitude number
---@param speed number
---@param durationOnStation number
@@ -258,7 +261,8 @@ do --setup route util
if racetrackSecondPoint then orbitType = "Race-Track" end
local basePoint = base:getPoint()
local points = {}
---@type Array<table>
local points
if racetrackSecondPoint == nil then
points = {
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
@@ -304,6 +308,7 @@ do --setup route util
end
local group = Group.getByName(groupName)
---@type Vec3?
local pos;
local i = 1
if group == nil then
@@ -393,6 +398,9 @@ do --setup route util
}, ""
end
---@param pointA Vec3
---@param pointB Vec3
---@return table, string?
ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB)
return {
id = "Mission",
@@ -461,7 +469,7 @@ do --setup route util
}
}
}
}, ""
}, nil
end
end
@@ -327,12 +327,13 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneId
if stageLaneIdentifier ~= nil then return end
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
---@type Array<Group>
local groups = {}
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
local group = player:getGroup()
if group then
groups[group:getID()] = group
table.insert(groups, group)
end
end
@@ -369,8 +370,11 @@ end
function GlobalStageManager:PrintMermaidStage()
local lanes = self._stageRepository:getAllStageLanes()
---@type table<string, string>
local nodes = {}
---@type table<string, string>
local edges = {}
---@type table<string, Array<number>>
local stageIndicesByLane = {} -- Track all stage indices per lane
local mainLaneId = "default"
@@ -403,7 +407,9 @@ function GlobalStageManager:PrintMermaidStage()
"#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise
}
---@type table<string, string>
local laneColorMap = {} -- Map lane ID to color
---@type number
local colorIndex = 1
-- Build nodes and collect all stage indices per lane
@@ -430,6 +436,7 @@ function GlobalStageManager:PrintMermaidStage()
local stageName = stage.stageName or stage.zoneName
-- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes
---@type string
local bracketLabel
if laneId == "default" then
bracketLabel = "[" .. stageIndex .. "]"
@@ -496,7 +503,7 @@ function GlobalStageManager:PrintMermaidStage()
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
-- For each stage in the side lane
for _, stageIdx in ipairs(sideIndices) do
@@ -528,7 +535,7 @@ function GlobalStageManager:PrintMermaidStage()
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then
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
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage
@@ -91,9 +91,9 @@ function SpearheadGroup:GetCoalition()
end
---comment
---@return table result list of objects
---@return Array<Unit|StaticObject> result list of objects
function SpearheadGroup:GetObjects()
---@type Array<Unit|StaticObject>
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName)
@@ -113,6 +113,7 @@ end
function StageBase:SpawnRedUnits()
---comment
---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
@@ -142,6 +143,7 @@ end
function StageBase:SpawnBlueUnits()
---comment
---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
@@ -37,6 +37,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
---@param params UnpackCrateParam
---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
@@ -104,6 +105,7 @@ function BuildableZone:OnCrateDroppedOff(_, kilos)
---@param params UnpackCrateParam
---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
+12 -9
View File
@@ -4,8 +4,8 @@
---@field private _isDefaultStageLane boolean
---@field private _stageLaneIdentifier string
---@field private _activeStageIndex number
---@field private _stagesInLaneByIndex table<number, Array<Stage>>
---@field private _chapterStarts table<number, boolean>?
---@field private _stagesInLaneByIndex table<string, Array<Stage>>
---@field private _chapterStarts table<string, boolean>?
---@field private _stageLaneState StageLaneState
---@field private _maxStageIndex number
local StageLane = {}
@@ -16,6 +16,7 @@ StageLane.__index = StageLane
---| "BetweenChapters"
---| "Completed"
---@type string?
StageLane.DefaultLaneKey = nil
function StageLane.New(laneIdentifier)
@@ -37,21 +38,21 @@ end
---@param stage Stage
function StageLane:AddStage(stage)
local stageIndex = stage:GetStageIndex()
if not self._stagesInLaneByIndex[stageIndex] then
self._stagesInLaneByIndex[stageIndex] = {}
if not self._stagesInLaneByIndex[tostring(stageIndex)] then
self._stagesInLaneByIndex[tostring(stageIndex)] = {}
end
if not self._maxStageIndex or stageIndex > self._maxStageIndex then
self._maxStageIndex = stageIndex
end
table.insert(self._stagesInLaneByIndex[stageIndex], stage)
table.insert(self._stagesInLaneByIndex[tostring(stageIndex)], stage)
end
---@param stageNumber number
---@return boolean?
function StageLane:IsStageIndexComplete(stageNumber)
local stages = self._stagesInLaneByIndex[stageNumber]
local stages = self._stagesInLaneByIndex[tostring(stageNumber)]
if not stages then
return nil
end
@@ -88,7 +89,7 @@ end
---@param stageNumber number
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
elseif stageNumber > self._maxStageIndex then
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>
--- 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
---@return boolean
function StageLane:IsChapterStart(stageNumber)
if self._chapterStarts == nil then
@@ -123,13 +125,14 @@ end
function StageLane:FillChapterStarts()
local previousIndex = nil
---@type table<string, number>
local stageIndices = {}
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
table.insert(stageIndices, tonumber(stageIndex))
end
table.sort(stageIndices)
self._chapterStarts = self._chapterStarts or {}
if self._chapterStarts == nil then self._chapterStarts = {} end
for _, stageIndex in ipairs(stageIndices) do
if previousIndex == nil then
-- First stage is always a chapter start
@@ -158,7 +161,7 @@ end
---@param stageIndex number
---@return Array<Stage>?
function StageLane:GetStagesAtIndex(stageIndex)
return self._stagesInLaneByIndex[stageIndex]
return self._stagesInLaneByIndex[tostring(stageIndex)]
end
---@return number?
@@ -162,6 +162,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
---comment
---@param selfA Stage
---@param time number?
---@return number|nil
self.CheckContinuousAsync = function (selfA, time)
selfA:CheckAndUpdateSelf()
@@ -325,7 +326,9 @@ function Stage:GetStageName()
return self.stageName
end
---@return Array<Mission>
function Stage:GetMissions()
---@type Array<Mission>
local missions = {}
for _, mission in pairs(self._db.missions) do
table.insert(missions, mission)
@@ -432,6 +435,12 @@ function Stage:PreActivate()
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
airbase:ActivateRedStage()
end
@@ -674,6 +683,7 @@ function Stage:ActivateBlueStage()
end
---@param selfA Stage
---@return number|nil
local ActivateBlueAsync = function(selfA)
pcall(function()
selfA:MarkStage()
@@ -54,7 +54,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
if lineThickness == nil or lineThickness <= 0 then
lineStyle = 0
end
---@type string
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in pairs(points) do
@@ -125,14 +125,14 @@ function DrawingHelper.DrawPolygon(object)
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
---@type Array<number>
local keys = {}
for k, _ in pairs(free.points) do
table.insert(keys, k)
end
table.sort(keys, function(a, b) return a < b end)
---@type Array<Vec3>
local points = {}
for _, k in ipairs(keys) do
local point = free.points[k]
@@ -274,6 +274,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
end
---@param lineStyle string
---@return integer
function DrawingHelper.ToLineStyleInteger(lineStyle)
lineStyle = lineStyle:lower()
if lineStyle == "no line" then
@@ -36,6 +36,7 @@ end
---@param self BattleManager
---@param time number
---@return number?
local function CheckTask(self, time)
local interval = self:Update()
if not interval then return end
@@ -148,7 +149,7 @@ end
---@return number
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
@@ -162,9 +163,9 @@ function BattleManager:getBestAmmo(unit)
end
end
local entry = Util.randomFromList(shells)
local entry = Util.randomFromList(shells) --[[@as table]]
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
return 258503344128, 1
else
@@ -209,6 +210,7 @@ function BattleManager:ToShootingHulls(groups)
end
local hulls = Util.getSeparatedConvexHulls(points, 50)
---@type Array<Array<Vec2>>
local enlargedHulls = {}
for _, hull in pairs(hulls) do
local enlarged = Util.enlargeConvexHull(hull, 25)
@@ -7,7 +7,7 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel
local StageConfig = require("classes.configuration.StageConfig")
---@class MissionCommandsHelper
---@class MissionCommandsHelper : OnPlayerEnterUnitListener
---@field missionsByCode table<string, Mission> @table of 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
@@ -210,7 +210,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do
if enabled == true then
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)
end
end
@@ -230,7 +230,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do
if enabled == true then
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)
end
end
@@ -345,7 +345,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
for code, enabled in pairs(self.enabledByCode) do
if enabled == true then
local mission = self.missionsByCode[code]
if mission and mission.priority == "primary" then
if mission and mission:GetPriority() == "primary" then
table.insert(primaryMissions, mission)
end
end
@@ -369,12 +369,12 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
do --- secondary missions
local count = 0
local path = { [1] = folderNames.secondary }
---@type Array<Mission>
local secondaryMissions = {}
for code, enabled in pairs(self.enabledByCode) do
if enabled == true then
local mission = self.missionsByCode[code]
if mission and mission.priority == "secondary" then
if mission and mission:GetPriority() == "secondary" then
table.insert(secondaryMissions, mission)
end
end
@@ -416,7 +416,7 @@ function MissionCommandsHelper:addMissionCommands(groupId, path, mission)
end
local missionFolderName = "[" ..
mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )"
mission.code .. "]" .. distance .. mission.name .. "(" .. mission.missionTypeDisplay .. ")"
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
table.insert(path, missionFolderName)
@@ -76,14 +76,15 @@ local SupplyConfigHelper = {}
---@return SupplyConfig?
function SupplyConfigHelper.fromObjectName(name)
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
end
end
return nil
end
---@param type CrateType
---@param type CrateType
---@return SupplyConfig?
function SupplyConfigHelper.getSupplyConfig(type)
return SupplyConfig[type]
end
@@ -10,7 +10,7 @@ local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig"
---@field enteredSupplyHub 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 _cargoInUnits table<string, table<CrateType, number>>
---@field private _logger Logger
@@ -43,6 +43,8 @@ function SupplyUnitsTracker.getOrCreate()
SpearheadEvents.AddOnPlayerEnterUnitListener(singleton)
---@param selfA SupplyUnitsTracker
---@param time number
---@return number?
local function updateTask(selfA, time)
selfA:Update()
@@ -134,6 +136,7 @@ end
---@private
---@param unit Unit
---@return boolean
function SupplyUnitsTracker:IsSupplyUnit(unit)
if unit == nil then return false end
@@ -158,15 +161,17 @@ function SupplyUnitsTracker:AddCargoToUnit(unitID, crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil then return end
if self._cargoInUnits[unitID] == nil then
self._cargoInUnits[unitID] = {}
local unitIdStr = tostring(unitID)
if self._cargoInUnits[unitIdStr] == nil then
self._cargoInUnits[unitIdStr] = {}
end
if self._cargoInUnits[unitID][crateType] == nil then
self._cargoInUnits[unitID][crateType] = 0
if self._cargoInUnits[unitIdStr][crateType] == nil then
self._cargoInUnits[unitIdStr][crateType] = 0
end
self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1
self._cargoInUnits[unitIdStr][crateType] = self._cargoInUnits[unitIdStr][crateType] + 1
end
@@ -253,7 +258,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end
end
self._unitPositions[unit:getID()] = pos
self._unitPositions[tostring(unit:getID())] = pos
end
end
end
@@ -323,6 +328,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self:UpdateWeightForUnit(unit)
cargoCount = cargoCount + 1
---@type {name: string, type: string, x: number, y: number}
local cargoSpawnObject = {
name = crateType .. "_" .. cargoCount,
type = cargoConfig.staticType,
@@ -484,7 +490,7 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
end
local objPos = foundObject:getPoint()
local box = desc.box
local box = desc.box --[[@type table]]
local heading = 0
-- Try to get object heading from position vector's forward direction
@@ -497,10 +503,10 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
end)
-- For rotated objects, we need to rotate the bounding box
local minX = box.min.x
local maxX = box.max.x
local minZ = box.min.z
local maxZ = box.max.z
local minX = box.min.x --[[@as number]]
local maxX = box.max.x --[[@as number]]
local minZ = box.min.z --[[@as number]]
local maxZ = box.max.z --[[@as number]]
-- If object has significant rotation, apply rotation to bbox corners
if math.abs(heading) > 0.1 then
@@ -517,8 +523,8 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
minZ, maxZ = math.huge, -math.huge
for _, corner in ipairs(corners) do
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading)
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(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) --[[@as number]]
minX = math.min(minX, rotX)
maxX = math.max(maxX, rotX)
minZ = math.min(minZ, rotZ)
@@ -587,7 +593,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
return nil
end
local crateRelativeBBox = crateDesc.box
local crateRelativeBBox = crateDesc.box --[[@as table]]
-- Get drop zone config for this unit
local dropZones = {
@@ -610,8 +616,11 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
radius = 100 -- Search a large area
}
}
---@type Array<{ pos: { x: number, y: number, z: number }, bbox: { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } } }>
local occupiedObjects = {}
---@param foundItem Object
---@param _ any
local found = function(foundItem, _)
local bbox = self:GetBoundingBoxes(foundItem)
if bbox then
@@ -660,6 +669,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
-- 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 = {
min = {
x = candidateX + crateRelativeBBox.min.x,
@@ -25,6 +25,8 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
local BuildableMission = {}
BuildableMission.__index = BuildableMission
---@param siteType string
---@param coords string
---@return string
local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
@@ -43,6 +45,7 @@ end
---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger
---@param briefing string?
---@return BuildableMission
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
setmetatable(BuildableMission, Mission)
@@ -117,6 +120,7 @@ function BuildableMission:ShowBriefing(groupID)
local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
local siteType = "FARP"
if self._crateType == "SAM_CRATE" then
@@ -143,17 +147,20 @@ function BuildableMission:ShowBriefing(groupID)
trigger.action.outTextForGroup(groupID, briefing, GlobalConfig:getBriefingTime())
end
---@param groupID number
function BuildableMission:MarkMissionAreaToGroup(groupID)
if self._markIDsPerGroup[groupID] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupID])
end
local groupIdStr = tostring(groupID)
if self._markIDsPerGroup[groupIdStr] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupIdStr])
end
---@type string
local text = "[" .. self.code .. "] " .. self.name .. " | " .. self._crateType
local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y }
local markID = DcsUtil.AddMarkToGroup(groupID, text, location)
self._markIDsPerGroup[groupID] = markID
self._markIDsPerGroup[groupIdStr] = markID
end
---@private
@@ -197,6 +204,7 @@ function BuildableMission:SpawnActive()
---@param selfA BuildableMission
---@param time number
---@return number?
local checkForCrateTasks = function (selfA, time)
selfA:CheckCratesInZone()
@@ -251,7 +259,7 @@ function BuildableMission:CheckCratesInZone()
local crates = self._supplyUnitsTracker:GetCargoCratesDropped()
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()
if Util.is3dPointInZone(pos, self._dropOffZone) then
@@ -24,6 +24,7 @@ local RunwayStrikeMission = {}
---@param runway Runway
---@param database Database
---@param logger Logger
---@param airbaseName string
---@param runwayBombingTracker RunwayBombingTracker
---@return RunwayStrikeMission?
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
@@ -205,8 +206,9 @@ function RunwayStrikeMission:StartRepair()
self._repairInProgress = true
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
---comment
---@param selfA any
---@return unknown
---@param selfA RunwayStrikeMission
---@param time number
---@return number?
local repairTask = function (selfA, time)
local interval = selfA:DoRepairCycle()
if interval == nil then return nil end
@@ -215,6 +217,7 @@ function RunwayStrikeMission:StartRepair()
timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
end
---@return number?
function RunwayStrikeMission:DoRepairCycle()
local interval = 5
@@ -247,12 +250,14 @@ function RunwayStrikeMission:DoRepairCycle()
end
---@class StaticSpawn
---@class StaticSpawn : table
---@field category string
---@field type string
---@field y number
---@field x number
---@field heading number
---@field name string?
---@field hidden boolean?
local counter = 1
@@ -441,6 +446,7 @@ end
---@private
---@param runway Runway
---@param numSections number
---@return Array<RunwaySection>
function RunwayStrikeMission:ToSections(runway, numSections)
@@ -14,7 +14,7 @@ local DcsUtil = require("classes.util.DcsUtil")
---@field private _completeAtIndex number
---@field private _parentStage Stage
---@field private _battleManager? BattleManager
---@field private _lastContactMarkerID number;
---@field private _lastContactMarkerID number
local ZoneMission = {}
--- @class MissionGroups
@@ -24,7 +24,7 @@ local ZoneMission = {}
--- @field unitsAlive table<string, table<string, boolean>>
--- @field targetsAlive table<string, table<string, boolean>>
--- @field sceneryTargets Array<SpearheadSceneryObject>
--- @field groupNamesPerunit table<string,string>
--- @field groupNamesPerUnit table<string,string>
---@class ParsedMissionName
---@field missionName string
@@ -36,7 +36,7 @@ local ZoneMission = {}
local function ParseZoneName(input)
local split_name = Util.split_string(input, "_")
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")
return nil
elseif split_length < 3 then
@@ -53,9 +53,10 @@ local function ParseZoneName(input)
if inputType == "cas" then parsedType = "CAS" end
if inputType == "bai" then parsedType = "BAI" end
if inputType == "sam" then parsedType = "SAM" end
if inputType == "deepstrike" then parsedType = "DEEPSTRIKE" end
if parsedType == "nil" then
MissionEditorWarnings.Add("Mission with zonename '" ..
MissionEditorWarnings.Add("Mission with zoneName '" ..
input .. "' has an unsupported type '" .. (type or "nil"))
return nil
end
@@ -66,8 +67,6 @@ local function ParseZoneName(input)
}
end
MINIMAL_UNITS_ALIVE_RATIO = 0.21
---comment
---@param zoneName string
---@param priority MissionPriority
@@ -112,7 +111,7 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
unitsAlive = {},
targetsAlive = {},
hasTargets = false,
groupNamesPerunit = {},
groupNamesPerUnit = {},
sceneryTargets = {}
}
@@ -150,17 +149,17 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
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
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
self._missionGroups.unitsAlive[groupName] = {}
end
self._missionGroups.unitsAlive[groupName][unitName] = true
self._missionGroups.groupNamesPerunit[unitName] = groupName
self._missionGroups.groupNamesPerUnit[unitName] = groupName
if isGroupTarget == true or isUnitTarget == true then
self._missionGroups.hasTargets = true
@@ -180,7 +179,6 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
if self.missionType == "CAS" then
self._battleManager = BattleManager.New(self._missionGroups.redGroups, self._missionGroups.blueGroups, self.zoneName, self._logger.LogLevel)
end
self._logger:debug("Mission " .. self.name .. " group count: " .. Util.tableLength(missionData.RedGroups))
@@ -229,10 +227,9 @@ end
---@internal
---@param checkHealth boolean
---@param messageIfDone boolean
function ZoneMission:UpdateState(checkHealth, messageIfDone)
---@param _ boolean
function ZoneMission:UpdateState(checkHealth, _)
if checkHealth == nil then checkHealth = false end
if messageIfDone == false then messageIfDone = true end
if checkHealth == true then
@@ -240,7 +237,7 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
local staticObject = StaticObject.getByName(unitName)
if staticObject 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
self._logger:debug("exploding unit")
trigger.action.explosion(staticObject:getPoint(), 100)
@@ -286,7 +283,9 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
end
if self._missionGroups.hasTargets == true then
---@type number
local total = 0
---@type number
local alive = 0
for _, units in pairs(self._missionGroups.targetsAlive) do
@@ -299,13 +298,13 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
end
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
total = total + 1
total = total + 1 --[[@as number]]
if sceneryObject:IsAlive() == true then
alive = alive + 1
end
end
local deadRatio = (total - alive) / total
local deadRatio = (total - alive) / total --[[@as number]]
if deadRatio >= self._completeAtIndex then
self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex)
self._state = "COMPLETED"
@@ -354,6 +353,19 @@ end
function ZoneMission:SpawnInactive()
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
group:Spawn()
end
@@ -390,6 +402,17 @@ function ZoneMission:SpawnActive()
self._battleManager:Start()
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:StartCheckingContinuous()
@@ -477,16 +500,18 @@ function ZoneMission:OnUnitLost(object)
local category = Object.getCategory(object)
if category == Object.Category.UNIT then
object = object --[[@as Unit]]
local unitName = object:getName()
self._logger:debug("UnitName:" .. unitName)
local groupName = self._missionGroups.groupNamesPerunit[unitName]
local groupName = self._missionGroups.groupNamesPerUnit[unitName]
self._missionGroups.unitsAlive[groupName][unitName] = false
if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then
self._missionGroups.targetsAlive[groupName][unitName] = false
end
elseif category == Object.Category.STATIC then
object = object --[[@as StaticObject]]
local name = object:getName()
self._missionGroups.unitsAlive[name][name] = false
@@ -9,7 +9,7 @@ local GlobalConfig = require("classes.configuration.GlobalConfig")
---@field zoneName string
---@field missionType MissionType
---@field missionTypeDisplay string
---@field priority MissionPriority
---@field protected _priority MissionPriority
---@field location Vec2?
---@field code string
---@field protected _state MissionState
@@ -40,7 +40,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.zoneName = zoneName
self.name = missionName
self.missionType = missionType
self.priority = priority
self._priority = priority
self._state = "NEW"
self._logger = logger
self._database = database
@@ -64,14 +64,19 @@ end
--region PUBLIC
---@return MissionPriority
function Mission:GetPriority()
return self._priority
end
function Mission:SpawnPersistedState() end
function Mission:SpawnActive() end
---comment
---@param checkHealth boolean
---@param messageIfDone boolean
---@param _checkHealth boolean
---@param _messageIfDone boolean
---@diagnostic disable-next-line: unused-local
function Mission:UpdateState(checkHealth, messageIfDone) end
function Mission:UpdateState(_checkHealth, _messageIfDone) end
function Mission:StartCheckingContinuous() end
function Mission:PercentageComplete()
@@ -86,6 +91,7 @@ function Mission:ShowBriefing(groupId)
local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
self._logger:debug("Coords converted: " .. coords)
local stateString = self:ToStateString()
@@ -113,7 +119,7 @@ end
function Mission:NotifyMissionComplete()
self._missionCommandsHelper:RemoveMissionToCommands(self)
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
pcall(function()
@@ -132,9 +138,9 @@ function Mission:ForceMissionComplete()
self:NotifyMissionComplete()
end
---@param groupId number
---@param _groupId number
---@diagnostic disable-next-line: unused-local
function Mission:MarkMissionAreaToGroup(groupId) end
function Mission:MarkMissionAreaToGroup(_groupId) end
---endregion
@@ -146,29 +152,27 @@ function Mission:ToStateString() return "status: in progress" end
--endregion
do --aliases
--- @alias MissionPriority
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionPriority
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "DEEPSTRIKE"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
---
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
end
return Mission
+58 -22
View File
@@ -32,6 +32,10 @@ do -- INIT DCS_UTIL
---| "Cilinder"
---| "Polygon"
---@class KeyValuePair
---@field key string
---@field value any
---@class SpearheadTriggerZone
---@field name string
---@field location Vec2
@@ -40,7 +44,7 @@ do -- INIT DCS_UTIL
---@field zone_type SpearheadTriggerZoneType
---@field properties Array<KeyValuePair>?
---@type Array<SpearheadTriggerZone>
---@type table<string, SpearheadTriggerZone>
DCS_UTIL.__trigger_zones = {}
end
@@ -66,12 +70,15 @@ do -- INIT DCS_UTIL
STATIC = 5 --CUSTOM CATEGORY
}
---@type table<string, string>
DCS_UTIL.__airbaseNamesById = {}
---@type table<string, SpearheadTriggerZone>
DCS_UTIL.__airbaseZonesByName = {}
---@type table<string, CoalitionSide>
DCS_UTIL.__airportsStartingCoalition = {}
---@type table<string, CoalitionSide>
DCS_UTIL.__warehouseStartingCoalition = {}
function DCS_UTIL.__INIT()
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
for _, kvPair in pairs(trigger_zone.properties) do
local key = kvPair["key"]
local value = kvPair["value"]
local key = kvPair.key
local value = kvPair.value
zone.properties[#zone.properties + 1] = { key = key, value = value }
end
end
@@ -116,6 +123,7 @@ do -- INIT DCS_UTIL
do -- init airports and warehouses
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
if warehouse_id ~= nil then
warehouse_id = tostring(warehouse_id) or "nil"
@@ -126,12 +134,12 @@ do -- INIT DCS_UTIL
end
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
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)
DCS_UTIL.__warehouseStartingCoalition[warehouse_id] = coalitionNumber
DCS_UTIL.__warehouseStartingCoalition[warehouse_id_str] = coalitionNumber
end
end
end
@@ -218,11 +226,13 @@ do -- INIT DCS_UTIL
end
end
--- takes a list of units and returns all the units that are in any of the zones
---@param unit_names table unit names
---@param zone_names table zone names
---@return table unit list of objects { unit = UNIT, zone_name = zoneName}
---@param unit_names Array<string> unit names
---@param zone_names Array<string> zone names
---@return table<number, { unit : Unit|StaticObject, zone_name: string }>
function DCS_UTIL.getUnitsInZones(unit_names, zone_names)
---@type Array<Unit|StaticObject>
local units = {}
---@type Array<SpearheadTriggerZone>
@@ -241,14 +251,19 @@ do -- INIT DCS_UTIL
zones[#zones + 1] = zone
end
end
---@type table<number, { unit : Unit|StaticObject, zone_name: string }>
local in_zone_units = {}
for units_ind = 1, #units do
local lUnit = units[units_ind]
local unit_pos = lUnit:getPosition().p
local isActive = true
local lCat = Object.getCategory(lUnit)
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 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
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)
if isInZone == true then
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
---@param group_names table unit 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)
local zone = DCS_UTIL.__trigger_zones[zone_name]
local zone = DCS_UTIL.__trigger_zones[zone_name] --[[@as SpearheadTriggerZone?]]
if zone == nil then
return {}
end
@@ -277,11 +292,13 @@ do -- INIT DCS_UTIL
---@param zone SpearheadTriggerZone
---@return Array<string> groupnames list of groups that are in the zone
function DCS_UTIL.areGroupsInCustomZone(group_names, zone)
---@type Array<{ unit : Unit|StaticObject, groupname: string }>
local units = {}
if Util.tableLength(group_names) < 1 then return {} end
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])
if group ~= nil then
entry = { unit = group:getUnit(1), groupname = group_names[k] }
@@ -290,7 +307,7 @@ do -- INIT DCS_UTIL
end
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
@@ -308,8 +325,8 @@ do -- INIT DCS_UTIL
--- takes a x, y poistion and checks if it is inside any of the zones
---@param x number North South position
---@param z number West East position
---@param zone_names table zone names
---@return table zones list of objects { zone_name = zoneName}
---@param zone_names Array<string> zone names
---@return Array<string> zones list of objects { zone_name = zoneName}
function DCS_UTIL.isPositionInZones(x, z, zone_names)
---@type Array<SpearheadTriggerZone>
local zones = {}
@@ -319,7 +336,7 @@ do -- INIT DCS_UTIL
zones[#zones + 1] = zone
end
end
---@type Array<string>
local result_zones = {}
for _, zone in pairs(zones) do
if Util.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then
@@ -396,6 +413,18 @@ do -- INIT DCS_UTIL
return nil;
end
---@return Array<string>
function DCS_UTIL.getAllGroupCategoryNames()
return {
"airplane",
"helicopter",
"ground",
"ship",
"train",
"static"
}
end
---@type table<string, CoordType>
local config =
{
@@ -408,7 +437,8 @@ do -- INIT DCS_UTIL
---@param location Vec2
---@param unitType string
---@param unitType string?
---@return string?
function DCS_UTIL.convertVec2ToUnitUsableType(location, unitType)
local height = land.getHeight(location)
@@ -429,6 +459,7 @@ do -- INIT DCS_UTIL
---@private
---@param location Vec3
---@param coordType CoordType
---@return string?
function DCS_UTIL.convertToDisplayCoord(location, coordType)
local lattitude, longitude, altitude = coord.LOtoLL(location)
@@ -440,7 +471,7 @@ do -- INIT DCS_UTIL
-- Convert DD to DDM (Degrees Decimal Minutes)
local function dd_to_ddm(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
return degrees * sign, minutes
end
@@ -559,6 +590,7 @@ do -- INIT DCS_UTIL
end
---@param group Group
---@return string? unit type name
function DCS_UTIL.getUnitTypeFromGroup(group)
for _, unit in pairs(group:getUnits()) do
if unit and unit:isExist() then
@@ -571,6 +603,7 @@ do -- INIT DCS_UTIL
---comment Get all units that are players
---@return Array<Unit> units
function DCS_UTIL.getAllPlayerUnits()
---@type Array<Unit>
local units = {}
for i = 0, 2 do
local players = coalition.getPlayers(i)
@@ -616,6 +649,7 @@ do -- INIT DCS_UTIL
return result
end
---@param unitName string
function DCS_UTIL.CleanCorpse(unitName)
unitName = "dead_" .. unitName
local object = StaticObject.getByName(unitName)
@@ -734,6 +768,8 @@ do -- INIT DCS_UTIL
return false
end
---@param groupName string
---@param offset number?
---@return boolean
function DCS_UTIL.IsBingoFuel(groupName, offset)
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")
--- @alias LogLevel
--- | "DEBUG"
--- | "INFO"
--- | "WARN"
--- | "ERROR"
--- | "NONE"
---@type LogLevel
local defaultLogLevel = "INFO"
+7 -3
View File
@@ -1,8 +1,12 @@
---@class MissionEditingWarnings
local MissionEditingWarnings = {}
---@type table<number, string>
MissionEditingWarnings.warnings = {}
function MissionEditingWarnings.Add(warningMessage)
table.insert(MissionEditingWarnings, warningMessage or "skip")
table.insert(MissionEditingWarnings.warnings, warningMessage or "skip")
end
---@param logger Logger
@@ -12,12 +16,12 @@ function MissionEditingWarnings.WriteAll(logger)
return
end
if not MissionEditingWarnings or #MissionEditingWarnings == 0 then
if not MissionEditingWarnings.warnings or #MissionEditingWarnings.warnings == 0 then
return
end
logger:warn("Mission Editor Warnings:")
for _, warning in ipairs(MissionEditingWarnings) do
for _, warning in ipairs(MissionEditingWarnings.warnings) do
logger:warn("- " .. warning)
end
+50 -27
View File
@@ -1,13 +1,13 @@
---@class Util
local UTIL = {}
do -- INIT UTIL
---splits a string in sub parts by seperator
---splits a string in sub parts by separator
---@param input string
---@param seperator string
---@return table result list of strings
function UTIL.split_string(input, seperator)
if seperator == nil then
seperator = " "
---@param separator string
---@return Array<string> result list of strings
function UTIL.split_string(input, separator)
if separator == nil then
separator = " "
end
local result = {}
@@ -15,14 +15,14 @@ do -- INIT UTIL
return result
end
for str in string.gmatch(input, "[^" .. seperator .. "]+") do
for str in string.gmatch(input, "[^" .. separator .. "]+") do
table.insert(result, str)
end
return result
end
---comment
---@param table any
---@param table table<any, any>
---@return number
function UTIL.tableLength(table)
if table == nil then return 0 end
@@ -35,23 +35,34 @@ do -- INIT UTIL
---@param orig table
---@return table copy
function UTIL.deepCopyTable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[UTIL.deepCopyTable(orig_key)] = UTIL.deepCopyTable(orig_value)
---@param original any
---@return any
local function deepCopy(original)
local orig_type = type(original)
---@type any
local copy
if orig_type == 'table' then
original = original --[[@as table<any, any>]]
copy = {} --[[@as table<any, any>]]
for orig_key, orig_value in pairs(original) do
local copiedKey = deepCopy(orig_key) --[[@as any]]
copy[copiedKey] = deepCopy(orig_value)
end
setmetatable(copy, deepCopy(getmetatable(original)))
else -- number, string, boolean, etc
copy = original --[[@as any]]
end
setmetatable(copy, UTIL.deepCopyTable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
return copy
end
return copy
return deepCopy(orig)
end
---Gets a random from the list
---@param list Array
---@return any @random element from the list
---@generic T
---@param list Array<T>
---@return T @random element from the list
function UTIL.randomFromList(list)
local max = #list
@@ -65,11 +76,13 @@ do -- INIT UTIL
return list[random]
end
---@param list Array
---@generic T
---@param list Array<T>
---@param start number start
---@param n number length
---@return Array
---@return Array<T>
function UTIL.sublist(list, start, n)
---@type table<number, any>
local result = {}
for i = start, n do
result[#result + 1] = list[i]
@@ -90,9 +103,10 @@ do -- INIT UTIL
end
local function table_print(tt, indent, done)
done = done or {}
done = done or {} --[[@as table<any, boolean>]]
indent = indent or 0
if type(tt) == "table" then
tt = tt --[[@as table<any, any>]]
local sb = {}
for key, value in pairs(tt) do
table.insert(sb, string.rep(" ", indent)) -- indent it
@@ -120,7 +134,7 @@ do -- INIT UTIL
---@param findable string
---@param ignoreCase boolean?
---@return boolean
UTIL.startswith = function(str, findable, ignoreCase)
UTIL.startsWith = function(str, findable, ignoreCase)
if ignoreCase == true then
return string.lower(str):find('^' .. string.lower(findable)) ~= nil
end
@@ -146,11 +160,11 @@ do -- INIT UTIL
---comment
---@param str string
---@param findableTable table
---@param findableTable table<any,any>
---@return boolean
UTIL.startswithAny = function(str, findableTable)
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
return false
end
@@ -237,13 +251,14 @@ do -- INIT UTIL
return ((vec1Norm.x * vec2Norm.x) + (vec1Norm.y * vec2Norm.y) + (vec1Norm.z * vec2Norm.z))
end
---@param polygon Array<Vec2> of pairs { x, y }
---@param x number X location
---@param y number Y location
---@return boolean
local function isInComplexPolygon(polygon, x, y)
---@param poly Array<Vec2> of pairs { x, y }
---@return Array<{ x1: number, z1: number, x2: number, z2: number }>
local function getEdges(poly)
local result = {}
for i = 1, #poly do
@@ -280,6 +295,7 @@ do -- INIT UTIL
---@param point Vec3
---@param zone SpearheadTriggerZone
---@return boolean
function UTIL.is3dPointInZone(point, zone)
if zone.zone_type == "Polygon" and zone.verts then
if UTIL.IsPointInPolygon(zone.verts, point.x, point.z) == true then
@@ -296,6 +312,7 @@ do -- INIT UTIL
---@param point Vec2
---@param zone SpearheadTriggerZone
---@return boolean
function UTIL.is2dPointInZone(point, zone)
if zone.zone_type == "Polygon" and zone.verts then
if UTIL.IsPointInPolygon(zone.verts, point.x, point.y) == true then
@@ -447,6 +464,8 @@ do -- INIT UTIL
end
---@param points Array<Vec2>
---@param meters number
---@return Array<Vec2>
function UTIL.enlargeConvexHull(points, meters)
if points == nil or #points == 0 then
return {}
@@ -524,6 +543,10 @@ function UTIL.GetTangentHullPointsFromOrigin(hull, origin)
return {}
end
---@param a Vec2
---@param b Vec2
---@param c Vec2
---@return number
local function orientation(a, b, c)
-- 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)