From bb9144e3a058f2221eb4f9d191f6eb9c0628dc59 Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Fri, 14 Aug 2026 18:57:33 +0000 Subject: [PATCH 01/16] updating develop branch now automatically creates beta release (#16) Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/16 Co-authored-by: dutchie031 --- .gitea/workflows/release-beta.yml | 89 +++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .gitea/workflows/release-beta.yml diff --git a/.gitea/workflows/release-beta.yml b/.gitea/workflows/release-beta.yml new file mode 100644 index 0000000..0173bb7 --- /dev/null +++ b/.gitea/workflows/release-beta.yml @@ -0,0 +1,89 @@ +name: Publish Release + +on: + push: + branches: + - develop + paths: + - src/** + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read Version from version.txt + id: read_version + run: | + VERSION=beta + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" + echo "Version: $VERSION" + + - name: Extract Release Notes + id: release_notes + run: | + VERSION=beta + RELEASE_NOTES=$(awk -v ver="Unreleased" ' + /^## \[/ { + if (found) exit + if (index($0, "[" ver "]") > 0) found = 1 + next + } + found { print } + ' RELEASENOTES.md) + + # Write multi-line output properly + echo "body<> "$GITHUB_OUTPUT" + echo "$RELEASE_NOTES" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Compile Spearhead + uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-action@action/v0 + with: + source-root: ./src + output-file: ./output/spearhead.lua + + - name: Remove existing tag if it exists + id: check_tag + run: | + TAG="${{ steps.read_version.outputs.tag }}" + if git tag | grep -q "^$TAG$"; then + echo "Tag exists. Removing it..." + git push origin :refs/tags/"$TAG" || true + git tag -d "$TAG" || true + fi + echo "Ready to create fresh tag: $TAG" + + - name: Apply Tag to Current Commit + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git tag ${{ steps.read_version.outputs.tag }} + git push origin ${{ steps.read_version.outputs.tag }} + + - name: Copy Release to Versioned File + run: | + cp ./output/spearhead.lua ./output/spearhead.${{ steps.read_version.outputs.tag }}.lua + echo "Versioned file created: spearhead.${{ steps.read_version.outputs.tag }}.lua" + + - name: Create Release and Upload Assets + uses: akkuman/gitea-release-action@v1 + with: + tag_name: ${{ steps.read_version.outputs.tag }} + name: Release ${{ steps.read_version.outputs.tag }} + body: ${{ steps.release_notes.outputs.body }} + prerelease: true + files: |- + ./output/spearhead.lua + ./output/spearhead.${{ steps.read_version.outputs.tag }}.lua + #token: ${{ secrets.GITHUB_TOKEN }} + env: + NODE_OPTIONS: '--experimental-fetch' + -- 2.54.0 From a1e36aa5d714eafd2df9139aa126190512530c34 Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sat, 15 Aug 2026 15:06:32 +0000 Subject: [PATCH 02/16] supplyHub Commands wiring and event triggers wired for more real time updates (#15) Issue: #10 - [x] Tested - [x] Reviewed - [x] ReleaseNotes UpdatedReviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/15 Co-authored-by: dutchie031 --- .vscode/settings.json | 25 --------- RELEASENOTES.md | 3 ++ config.lua | 2 +- src/classes/configuration/GlobalConfig.lua | 2 +- .../helpers/MissionCommandsHelper.lua | 51 ++++++++----------- .../helpers/SupplyUnitsTracker.lua | 23 ++++++++- .../missions/BuildableMission.lua | 2 +- 7 files changed, 47 insertions(+), 61 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7674b97..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "cSpell.words": [ - "farp", - "Sams", - "SEAD" - ], - "Lua.diagnostics.globals": [ - "lfs" - ], - "maptz.regionfolder": { - "[lua]" : { - "foldEnd": "--endregion", - "foldEndRegex": "[\\s]*--endregion", - "foldStart": "--region [NAME]", - "foldStartRegex": "[\\s]*--region[\\s]*(.*)", - "disableFolding": false - } - }, - "livePreview.serverRoot": "/_docs", - "Lua.workspace.library": [ - "/Users/ex61wi/Developer/personal/DcsMissionScriptingTools/dutchies-dcs-scripting-tools/lua-addons", - "c:\\Users\\Tim\\.vscode\\extensions\\dutchie031.dutchies-dcs-scripting-tools-0.0.3\\lua-addons" - ], - "dutchies-dcs-scripting-tools.compileAt": "onSave", -} \ No newline at end of file diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fb2d13d..7dd3c3d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,9 @@ ### Bug Fixes +- PR #15 + Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. + ## [0.12.1] 2026-07 diff --git a/config.lua b/config.lua index 44014cf..cc7da67 100644 --- a/config.lua +++ b/config.lua @@ -6,7 +6,7 @@ SpearheadConfig = { --- The time briefings should be displayed by default. --- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be - briefingMessageDuration = 30, --default 30 + briefingMessageDuration = 60, --default 60 CapConfig = { --quickly enable of disable the entire CAP Logic diff --git a/src/classes/configuration/GlobalConfig.lua b/src/classes/configuration/GlobalConfig.lua index 102703c..4e5ba74 100644 --- a/src/classes/configuration/GlobalConfig.lua +++ b/src/classes/configuration/GlobalConfig.lua @@ -25,7 +25,7 @@ function GlobalConfig.New() end function GlobalConfig:getBriefingTime() - return self._briefingTime or 30 + return self._briefingTime or 60 end diff --git a/src/classes/stageClasses/helpers/MissionCommandsHelper.lua b/src/classes/stageClasses/helpers/MissionCommandsHelper.lua index a519d0f..1328af3 100644 --- a/src/classes/stageClasses/helpers/MissionCommandsHelper.lua +++ b/src/classes/stageClasses/helpers/MissionCommandsHelper.lua @@ -14,7 +14,6 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel ---@field updateContinuous fun(self: MissionCommandsHelper, time: number): number @function to update commands continuously ---@field pinnedByGroup table @table of pinned missions by group ID ---@field private _stageBriefings table @table of stage briefings by stage name ----@field private _supplyHubGroups table @table of supply hub groups by their ID ---@field private _logger Logger @logger instance for logging ---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance local MissionCommandsHelper = {} @@ -49,11 +48,28 @@ function MissionCommandsHelper.getOrCreate(logLevel) instance.updateNeeded = false instance.pinnedByGroup = {} instance.lastUpdate = 0 - instance._supplyHubGroups = {} instance._stageBriefings = {} instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logLevel) + instance._supplyUnitsTracker:AddOnSupplyUnitEventListener( + { + enteredSupplyHub = function(self, unit) + if unit == nil then return end + instance.updateNeeded = true + instance:updateCommandsForGroup(unit:getGroup():getID()) + end, + exitedSupplyHub = function(self, unit) + instance.updateNeeded = true + instance:updateCommandsForGroup(unit:getGroup():getID()) + end, + supplyUnitSpawned = function(self, unit) + instance.updateNeeded = true + instance:updateCommandsForGroup(unit:getGroup():getID()) + end + } + ) + ---comment ---@param selfA MissionCommandsHelper ---@param time number @@ -108,32 +124,6 @@ function MissionCommandsHelper:RemoveMissionToCommands(mission) self.updateNeeded = true end ----@param groupID number -function MissionCommandsHelper:MarkUnitInSupplyHub(groupID) - self._logger:debug("Marking unit in supply hub: " .. tostring(groupID)) - local updateNeeded = false - if self._supplyHubGroups[tostring(groupID)] ~= true then - updateNeeded = true - end - - self._supplyHubGroups[tostring(groupID)] = true - if updateNeeded == true then self:updateCommandsForGroup(groupID) end -end - - ----@param groupID number -function MissionCommandsHelper:MarkUnitOutsideSupplyHub(groupID) - self._logger:debug("Marking unit outide supply hub: " .. tostring(groupID)) - local updateNeeded = false - if self._supplyHubGroups[tostring(groupID)] == true then - updateNeeded = true - end - - self._supplyHubGroups[tostring(groupID)] = false - if updateNeeded == true then self:updateCommandsForGroup(groupID) end -end - - ---@param unit Unit function MissionCommandsHelper:OnPlayerEntersUnit(unit) @@ -438,7 +428,7 @@ end ---@param groupID integer function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID) - if self._supplyHubGroups[tostring(groupID)] ~= true then return end + if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupID) ~= true then return end self._logger:debug("Adding supply hub commands for group: " .. tostring(groupID)) @@ -536,8 +526,7 @@ function MissionCommandsHelper:addMissionFolders(groupId) missionCommands.addSubMenuForGroup(groupId, folderNames.primary) missionCommands.addSubMenuForGroup(groupId, folderNames.secondary) - if self._supplyHubGroups[tostring(groupId)] == true then - self._logger:debug("Adding supply hub commands folder for group: " .. tostring(groupId)) + if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupId) == true then missionCommands.addSubMenuForGroup(groupId, folderNames.supplyHub) end diff --git a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua index 7362529..7188e2d 100644 --- a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua +++ b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua @@ -94,7 +94,7 @@ end ---@param listener SupplyUnitEventListener -function SupplyUnitsTracker:AddOnSupplyUnitSpawnedListener(listener) +function SupplyUnitsTracker:AddOnSupplyUnitEventListener(listener) if listener == nil then return end if self._supplyUnitEventsListeners == nil then @@ -104,6 +104,26 @@ function SupplyUnitsTracker:AddOnSupplyUnitSpawnedListener(listener) table.insert(self._supplyUnitEventsListeners, listener) end +---@param unit Unit +---@return boolean +function SupplyUnitsTracker:IsUnitInSupplyHub(unit) + if unit == nil then return false end + local unitIDStr = tostring(unit:getID()) + return self._unitInSupplyHub[unitIDStr] == true +end + +---@param groupID number +---@return boolean +function SupplyUnitsTracker:IsGroupLeadInSupplyHub(groupID) + local group = DcsUtil.GetPlayerGroupByGroupID(groupID) + if group == nil then return false end + + local unit = group:getUnit(1) + if unit == nil then return false end + + return self:IsUnitInSupplyHub(unit) +end + function SupplyUnitsTracker:Update() local players = DcsUtil.getAllPlayerUnits() for _, player in pairs(players) do @@ -211,7 +231,6 @@ function SupplyUnitsTracker:CheckUnitsInZones() if Util.is3dPointInZone(pos, zone) then if self._unitInSupplyHub[tostring(unit:getID())] ~= true then self._unitInSupplyHub[tostring(unit:getID())] = true - for _, listener in pairs(self._supplyUnitEventsListeners) do pcall(function() if listener.enteredSupplyHub then diff --git a/src/classes/stageClasses/missions/BuildableMission.lua b/src/classes/stageClasses/missions/BuildableMission.lua index c270945..4a7a8ac 100644 --- a/src/classes/stageClasses/missions/BuildableMission.lua +++ b/src/classes/stageClasses/missions/BuildableMission.lua @@ -197,7 +197,7 @@ function BuildableMission:SpawnActive() self._state = "ACTIVE" self._missionCommandsHelper:AddMissionToCommands(self) - self._supplyUnitsTracker:AddOnSupplyUnitSpawnedListener(self) + self._supplyUnitsTracker:AddOnSupplyUnitEventListener(self) local units = self._supplyUnitsTracker:GetUnits() if units then -- 2.54.0 From f677a83c8e802f4f905db46703938ffde0391eea Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sun, 16 Aug 2026 11:55:34 +0000 Subject: [PATCH 03/16] Crate spawning update (#17) Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/16 Co-authored-by: dutchie031 Issue: #11 -[ ] Tested -[ ] Reviewed -[ ] Release notes updatedReviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/17 --- .gitignore | 3 +- RELEASENOTES.md | 6 +- .../stageClasses/helpers/MaxLoadConfig.lua | 20 -- .../stageClasses/helpers/SupplyLoadConfig.lua | 42 +++ .../helpers/SupplyUnitsTracker.lua | 271 +++++++++++++++--- 5 files changed, 281 insertions(+), 61 deletions(-) delete mode 100644 src/classes/stageClasses/helpers/MaxLoadConfig.lua create mode 100644 src/classes/stageClasses/helpers/SupplyLoadConfig.lua diff --git a/.gitignore b/.gitignore index cd31b20..5d688a1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /dist -.vscode/settings.json \ No newline at end of file +.vscode/settings.json +**\settings.json \ No newline at end of file diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7dd3c3d..0537517 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,7 +7,11 @@ ### Bug Fixes -- PR #15 +- Issue #11
+ PR #17
+ Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
+ Additionally different units will spawn in different areas depending on loading side. +- PR #15
Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. diff --git a/src/classes/stageClasses/helpers/MaxLoadConfig.lua b/src/classes/stageClasses/helpers/MaxLoadConfig.lua deleted file mode 100644 index 0d7f407..0000000 --- a/src/classes/stageClasses/helpers/MaxLoadConfig.lua +++ /dev/null @@ -1,20 +0,0 @@ ----@class MaxLoadConfig ----@field maxInternalLoad number - ----@type table -local MaxLoadConfig = { - ["Mi-8MT"] = { - maxInternalLoad = 4000, - }, - ["CH-47Fbl1"] = { - maxInternalLoad = 10000 - }, - ["Mi-24P"] = { - maxInternalLoad = 2000 - }, - ["UH-1H"] = { - maxInternalLoad = 2000 - } -} - -return MaxLoadConfig \ No newline at end of file diff --git a/src/classes/stageClasses/helpers/SupplyLoadConfig.lua b/src/classes/stageClasses/helpers/SupplyLoadConfig.lua new file mode 100644 index 0000000..90b42db --- /dev/null +++ b/src/classes/stageClasses/helpers/SupplyLoadConfig.lua @@ -0,0 +1,42 @@ +---@class DropZoneSlice +---@field centerAngle number Angle in degrees (0=forward, 90=right, 180=rear, 270=left) +---@field angleWidth number Total width of the slice in degrees (e.g. 60 = ±30°) +---@field minRadius number Minimum search radius in meters (safe distance from helicopter) +---@field maxRadius number Maximum search radius in meters +---@field spacing number Distance increment when searching outward in meters + +---@class SupplyLoadConfig +---@field maxInternalLoad number +---@field dropZones Array + +---@type table +local SupplyLoadConfig = { + ["Mi-8MT"] = { + maxInternalLoad = 4000, + dropZones = { + { centerAngle = 180, angleWidth = 60, minRadius = 20, maxRadius = 50, spacing = 5 }, + } + }, + ["CH-47Fbl1"] = { + maxInternalLoad = 10000, + dropZones = { + { centerAngle = 180, angleWidth = 30, minRadius = 20, maxRadius = 75, spacing = 5 }, + } + }, + ["Mi-24P"] = { + maxInternalLoad = 2000, + dropZones = { + { centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, + { centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, + } + }, + ["UH-1H"] = { + maxInternalLoad = 2000, + dropZones = { + { centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, + { centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }, + } + } +} + +return SupplyLoadConfig \ No newline at end of file diff --git a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua index 7188e2d..d98d2e0 100644 --- a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua +++ b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua @@ -3,7 +3,7 @@ local Util = require("classes.util.Util") local DcsUtil = require("classes.util.DcsUtil") local SpearheadEvents = require("classes.spearhead_events") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") -local MaxLoadConfig = require("classes.stageClasses.helpers.MaxLoadConfig") +local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig") ---@class SupplyUnitEventListener ---@field supplyUnitSpawned fun(self:SupplyUnitEventListener, unit:Unit) | nil @@ -297,14 +297,15 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType) local unit = DcsUtil.GetPlayerUnitByID(unitID) - if unit == nil or unit:isExist() == false then return end + if unit == nil or unit:isExist() == false then + self._logger:warn("Unload requested for non-existent unit: " .. unitID) + return + end local group = unit:getGroup() if group == nil then + self._logger:warn("Unload requested for unit with no group: " .. unit:getName()) return end - - self:RemoveCargoFromUnit(unitID, crateType) - self:UpdateWeightForUnit(unit) local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType) @@ -313,7 +314,16 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe return end - local cargoPos = self:GetCargoPlacePosition(unit) + local cargoPos = self:GetCargoPlacePosition(unit, cargoConfig.staticType) + + if cargoPos == nil then + self._logger:warn("No valid position found to drop cargo for unit: " .. unit:getName()) + trigger.action.outTextForUnit(unit:getID(), "No valid position to drop cargo. Unloading area is too crowded.", 10) + return + end + + self:RemoveCargoFromUnit(unitID, crateType) + self:UpdateWeightForUnit(unit) cargoCount = cargoCount + 1 local cargoSpawnObject = { @@ -323,9 +333,11 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe y = cargoPos.z, } + self._logger:debug("Spawning crate #" .. cargoCount .. " at (" .. string.format("%.2f", cargoPos.x) .. ", " .. string.format("%.2f", cargoPos.y) .. ", " .. string.format("%.2f", cargoPos.z) .. ")") local spawned = coalition.addStaticObject(unit:getCoalition(), cargoSpawnObject) self._droppedCrates[cargoSpawnObject.name] = spawned missionCommandsHelper:updateCommandsForGroup(group:getID()) + self._logger:debug("Cargo dropped for unit: " .. unit:getName() .. " crateType: " .. crateType) end ---@return table @@ -413,7 +425,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper) end end - local unitConfig = MaxLoadConfig[unit:getTypeName()] + local unitConfig = SupplyLoadConfig[unit:getTypeName()] if unitConfig == nil then trigger.action.outTextForUnit(unit:getID(), "Your unit type is not configured for logistics: " .. crateType, 5) self._logger:error("Invalid unit type: " .. unit:getTypeName()) @@ -456,53 +468,234 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType) end end +---@class SupplyUnitsBoundingBox +---@field min Vec3 World space minimum +---@field max Vec3 World space maximum +---@field heading number? Object heading in radians + +---@param foundObject Object +---@return SupplyUnitsBoundingBox? +function SupplyUnitsTracker:GetBoundingBoxes(foundObject) + + local desc = foundObject:getDesc() + if foundObject:getCategory() == Object.Category.SCENERY then + desc = SceneryObject.getDescByName(foundObject:getTypeName()) + end + + if desc == nil or desc.box == nil then + return nil + end + + local objPos = foundObject:getPoint() + local box = desc.box + local heading = 0 + + -- Try to get object heading from position vector's forward direction + -- This works for units and other objects that support getPosition + pcall(function() + local objPosition = foundObject:getPosition() + if objPosition and objPosition.x then + heading = math.atan2(objPosition.x.z, objPosition.x.x) + end + 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 + + -- If object has significant rotation, apply rotation to bbox corners + if math.abs(heading) > 0.1 then + -- Get all 4 corners of bbox in local space + local corners = { + {minX, minZ}, + {minX, maxZ}, + {maxX, minZ}, + {maxX, maxZ} + } + + -- Rotate corners and find new min/max + minX, maxX = math.huge, -math.huge + 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) + minX = math.min(minX, rotX) + maxX = math.max(maxX, rotX) + minZ = math.min(minZ, rotZ) + maxZ = math.max(maxZ, rotZ) + end + end + + -- Convert relative bbox to world space by adding object position + ---@type SupplyUnitsBoundingBox + return { + min = { + x = objPos.x + minX, + y = objPos.y + box.min.y, + z = objPos.z + minZ + }, + max = { + x = objPos.x + maxX, + y = objPos.y + box.max.y, + z = objPos.z + maxZ + }, + heading = heading + } +end + +---Check if two axis-aligned bounding boxes collide with safety margin +---@param crateBBox SupplyUnitsBoundingBox The crate's bbox in world space +---@param objBBox SupplyUnitsBoundingBox The existing object's bbox in world space +---@param safetyMargin number Safety margin around objects +---@return boolean True if collision detected +function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin) + + -- Apply safety margin to object bbox + local objMin = { + x = objBBox.min.x - safetyMargin, + y = objBBox.min.y - safetyMargin, + z = objBBox.min.z - safetyMargin + } + local objMax = { + x = objBBox.max.x + safetyMargin, + y = objBBox.max.y + safetyMargin, + z = objBBox.max.z + safetyMargin + } + + -- AABB collision detection + return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and + crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and + crateBBox.min.z <= objMax.z and crateBBox.max.z >= objMin.z +end ---@private ---@param unit Unit ----@return Vec3 -function SupplyUnitsTracker:GetCargoPlacePosition(unit) +---@param crateTypeName string +---@return Vec3? +function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName) - local pos = unit:getPosition() - local preferredPos = { - x = pos.p.x - 10 * pos.x.x, - y = pos.p.y - 10 * pos.x.y, - z = pos.p.z - 10 * pos.x.z + local unitPos = unit:getPosition() + + -- Get unit's heading from the forward vector (x component) + -- Heading is calculated as: atan2(forward.z, forward.x) + local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x) + + -- Get crate bbox - relative to placement position + local crateDesc = StaticObject.getDescByName(crateTypeName) --[[@as table]] + if crateDesc == nil or crateDesc.box == nil then + self._logger:error("Could not get bbox for crate type: " .. crateTypeName) + return nil + end + + local crateRelativeBBox = crateDesc.box + + -- Get drop zone config for this unit + local dropZones = { + { centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 } + } + + if SupplyLoadConfig[unit:getTypeName()] ~= nil then + dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones + end + + -- Get occupied objects with their bboxes + local searchVolume = { + id = world.VolumeType.SPHERE, + params = { + point = { + x = unitPos.p.x, + y = unitPos.p.y, + z = unitPos.p.z + }, + radius = 100 -- Search a large area + } } - return preferredPos + local occupiedObjects = {} + local found = function(foundItem, val) + local bbox = self:GetBoundingBoxes(foundItem) + if bbox then + self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")") + table.insert(occupiedObjects, { + pos = foundItem:getPoint(), + bbox = bbox + }) + else + self._logger:debug("Found object without bbox: " .. foundItem:getTypeName()) + end + end + local searchCategories = {} + for key, value in pairs(Object.Category) do + self._logger:debug("Adding category to search: " .. tostring(value) .. " (" .. tostring(key) .. ")") + table.insert(searchCategories, value) + end - -- local volume = { - -- id = world.VolumeType.SPHERE, - -- params = { - -- point = preferredPos, - -- radius = 10 - -- } - -- } +---@diagnostic disable-next-line: param-type-mismatch + world.searchObjects(searchCategories, searchVolume, found) - -- local occupiedPosX = {} - -- local occupiedPosZ = {} + local safetyMargin = 3 -- Safety margin around objects - -- ---@param foundItem Object - -- local found = function(foundItem, val) + -- Search through each slice + for _, zone in ipairs(dropZones) do + -- Calculate angle range for this slice + local minAngle = zone.centerAngle - (zone.angleWidth / 2) + local maxAngle = zone.centerAngle + (zone.angleWidth / 2) - -- local foundPos = foundItem:getPoint() + -- Search outward in rings starting from minRadius + for distance = zone.minRadius, zone.maxRadius, zone.spacing do + -- Check multiple positions within the angular slice + local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections + + for angle = minAngle, maxAngle, angleStep do + local radians = math.rad(angle) + + -- Calculate position at this angle and distance, relative to unit's heading + -- Angle 0 = forward, 90 = right, 180 = rear, 270 = left + -- Apply unit heading to make angles relative to unit orientation + local worldAngle = radians + unitHeading + + local candidateX = unitPos.p.x + distance * math.sin(worldAngle) + local candidateZ = unitPos.p.z + distance * math.cos(worldAngle) + local candidateY = land.getHeight({ x = candidateX, y = candidateZ }) - -- local z = math.floor(foundPos.z) - -- for i = z - 3 , z + 3 do - -- occupiedPosZ[i] = true - -- end + -- Convert crate's relative bbox to world space at this position + local crateBBoxWorldSpace = { + min = { + x = candidateX + crateRelativeBBox.min.x, + y = candidateY + crateRelativeBBox.min.y, + z = candidateZ + crateRelativeBBox.min.z + }, + max = { + x = candidateX + crateRelativeBBox.max.x, + y = candidateY + crateRelativeBBox.max.y, + z = candidateZ + crateRelativeBBox.max.z + } + } - -- local x = math.floor(foundPos.x) - -- for i = x - 3 , x + 3 do - -- occupiedPosX[i] = true - -- end - -- end + -- Check if crate bbox collides with any existing objects + local collides = false + for _, obj in ipairs(occupiedObjects) do + if self:CheckBBoxCollision(crateBBoxWorldSpace, obj.bbox, safetyMargin) then + collides = true + self._logger:debug("Collision at angle=" .. angle .. ", distance=" .. distance) + break + end + end - -- world.searchObjects(volume.id, volume.params, found) + if not collides then + self._logger:debug("Valid position found at angle=" .. angle .. ", distance=" .. distance .. ", pos=(" .. string.format("%.2f", candidateX) .. ", " .. string.format("%.2f", candidateY) .. ", " .. string.format("%.2f", candidateZ) .. ")") + return { x = candidateX, y = candidateY, z = candidateZ } + end + end + end + end - - + -- No free spot found + return nil end return SupplyUnitsTracker \ No newline at end of file -- 2.54.0 From ab910b8faf35db99edfda7fc54d3c1008fb8219c Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sun, 16 Aug 2026 14:38:46 +0200 Subject: [PATCH 04/16] webhook embed in release action --- .gitea/workflows/release-beta.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitea/workflows/release-beta.yml b/.gitea/workflows/release-beta.yml index 0173bb7..14fd233 100644 --- a/.gitea/workflows/release-beta.yml +++ b/.gitea/workflows/release-beta.yml @@ -87,3 +87,15 @@ jobs: env: NODE_OPTIONS: '--experimental-fetch' + - name: Announce Release + uses: tsickert/discord-webhook@v7.0.0 + with: + webhook-url: ${{ secrets.WEBHOOK_URL }} + content: "New Beta Release" + username: "Spearhead Release Bot" + #TODO: avatar-url spearhead avatar + thread-id: 1538520488442331227 + embed-title: "Spearhead Release Beta" + embed-color: 3093247 + embed-description: + ${{ steps.release_notes.outputs.body }} -- 2.54.0 From b34918ff2898c9d4476dcd00fcde2a899d9e209f Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sun, 16 Aug 2026 14:40:49 +0200 Subject: [PATCH 05/16] webhook embed in release action --- .gitea/workflows/release-beta.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/release-beta.yml b/.gitea/workflows/release-beta.yml index 14fd233..4c568ae 100644 --- a/.gitea/workflows/release-beta.yml +++ b/.gitea/workflows/release-beta.yml @@ -6,6 +6,7 @@ on: - develop paths: - src/** + - .gitea/workflows/release-beta.yml workflow_dispatch: jobs: -- 2.54.0 From e5abfe19d629486ea1fe5ddf3d37b829230b0aa2 Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sun, 16 Aug 2026 14:44:55 +0200 Subject: [PATCH 06/16] webhook embed in release action --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0537517..eaf720d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,11 +7,11 @@ ### Bug Fixes -- Issue #11
- PR #17
- Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
+- Issue #11 + PR #17 + Supply crate spawning now checks for free space and will not spawn if the area is too crowded. Additionally different units will spawn in different areas depending on loading side. -- PR #15
+- PR #15 Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. -- 2.54.0 From 52f97f7c92168d0a6f5f87342f31c881af38653d Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Sun, 16 Aug 2026 14:45:14 +0200 Subject: [PATCH 07/16] webhook embed in release action --- .gitea/workflows/release-beta.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/release-beta.yml b/.gitea/workflows/release-beta.yml index 4c568ae..15120b7 100644 --- a/.gitea/workflows/release-beta.yml +++ b/.gitea/workflows/release-beta.yml @@ -100,3 +100,4 @@ jobs: embed-color: 3093247 embed-description: ${{ steps.release_notes.outputs.body }} + -- 2.54.0 From 8c75ba502720e61aa420bf08989324534b674dcf Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Mon, 17 Aug 2026 09:32:01 +0000 Subject: [PATCH 08/16] Fix/custom drawings (#18) Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/18 Co-authored-by: dutchie031 --- RELEASENOTES.md | 21 +-- src/classes/capClasses/taskings/CAP.lua | 10 +- .../taskings/Callbacks/GlobalCallbacks.lua | 26 ++++ src/classes/capClasses/taskings/INTERCEPT.lua | 4 +- src/classes/capClasses/taskings/SWEEP.lua | 4 +- src/classes/spearhead_db.lua | 2 +- src/classes/spearhead_routeutil.lua | 12 +- .../stageClasses/GlobalStageManager.lua | 3 + .../stageClasses/drawings/CustomDrawing.lua | 20 ++- .../drawings/helper/DrawingHelper.lua | 125 +++++++++++++----- src/classes/util/Util.lua | 43 ++++++ 11 files changed, 205 insertions(+), 65 deletions(-) create mode 100644 src/classes/capClasses/taskings/Callbacks/GlobalCallbacks.lua diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eaf720d..e8e479e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,22 +8,15 @@ ### Bug Fixes - Issue #11 - PR #17 Supply crate spawning now checks for free space and will not spawn if the area is too crowded. Additionally different units will spawn in different areas depending on loading side. -- PR #15 - Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. - - -## [0.12.1] 2026-07 - -### Breaking Changes - -### New Features - -### Bug Fixes - -- Fixed stage drawing to only be checked when a stage completed. Now done on stage number changed. + PR #17 +- Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. + PR #15 +- Fixed custom drawings not being drawn correctly. + PR #18 +- Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue. + PR #18 ## [0.12.0] 2026-06 diff --git a/src/classes/capClasses/taskings/CAP.lua b/src/classes/capClasses/taskings/CAP.lua index ecc8f2b..cb53bd2 100644 --- a/src/classes/capClasses/taskings/CAP.lua +++ b/src/classes/capClasses/taskings/CAP.lua @@ -220,7 +220,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig) action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" } } } @@ -260,7 +260,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig) }, stopCondition = { duration = durationBefore10, - condition = "return Spearhead.DcsUtil.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)", + condition = "return GlobalCapCallBacks.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)", } } }, @@ -273,7 +273,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig) action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")" } } } @@ -299,7 +299,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig) }, stopCondition = { duration = durationAfter10, - condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")", + condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", } } }, @@ -312,7 +312,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig) action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } } diff --git a/src/classes/capClasses/taskings/Callbacks/GlobalCallbacks.lua b/src/classes/capClasses/taskings/Callbacks/GlobalCallbacks.lua new file mode 100644 index 0000000..0c6096f --- /dev/null +++ b/src/classes/capClasses/taskings/Callbacks/GlobalCallbacks.lua @@ -0,0 +1,26 @@ + +local DcsUtil = require("classes.util.DcsUtil") +local Events = require("classes.spearhead_events") + + +GlobalCapCallBacks = {} + +function GlobalCapCallBacks.IsBingoFuel(groupName, fuelPercent) + return DcsUtil.IsBingoFuel(groupName, fuelPercent) +end + +function GlobalCapCallBacks.NeedsRTBInTen(groupName, fuelOffset) + return DcsUtil.NeedsRTBInTen(groupName, fuelOffset) +end + +function GlobalCapCallBacks.PublishRTBInTen(groupName) + return Events.PublishRTBInTen(groupName) +end + +function GlobalCapCallBacks.PublishRTB(groupName) + return Events.PublishRTB(groupName) +end + +function GlobalCapCallBacks.PublishOnStation(groupName) + return Events.PublishOnStation(groupName) +end diff --git a/src/classes/capClasses/taskings/INTERCEPT.lua b/src/classes/capClasses/taskings/INTERCEPT.lua index 8ed2600..e6cdc8d 100644 --- a/src/classes/capClasses/taskings/INTERCEPT.lua +++ b/src/classes/capClasses/taskings/INTERCEPT.lua @@ -247,7 +247,7 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } } @@ -379,7 +379,7 @@ function INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosi action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } } diff --git a/src/classes/capClasses/taskings/SWEEP.lua b/src/classes/capClasses/taskings/SWEEP.lua index ed2ee40..61e12aa 100644 --- a/src/classes/capClasses/taskings/SWEEP.lua +++ b/src/classes/capClasses/taskings/SWEEP.lua @@ -189,7 +189,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig) action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" } } } @@ -270,7 +270,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig) action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } } diff --git a/src/classes/spearhead_db.lua b/src/classes/spearhead_db.lua index 85ed3d9..653d1b2 100644 --- a/src/classes/spearhead_db.lua +++ b/src/classes/spearhead_db.lua @@ -231,7 +231,7 @@ function Database.New(Logger) for key, layer_object in pairs(layer.objects) do if Util.startswith(layer_object.name, "drawing_", true) then local object = layer_object --[[@as DrawingObject]] - local customDrawing = CustomDrawing.New(object) + local customDrawing = CustomDrawing.New(object, nil, self._logger.LogLevel) table.insert(self._tables.CustomDrawings, customDrawing) end end diff --git a/src/classes/spearhead_routeutil.lua b/src/classes/spearhead_routeutil.lua index 6e5f143..717ee5b 100644 --- a/src/classes/spearhead_routeutil.lua +++ b/src/classes/spearhead_routeutil.lua @@ -92,7 +92,7 @@ do --setup route util action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" } } } @@ -122,7 +122,7 @@ do --setup route util }, stopCondition = { duration = durationBefore10, - condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\", 0.10)", + condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", } } }, @@ -135,7 +135,7 @@ do --setup route util action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")" } } } @@ -156,7 +156,7 @@ do --setup route util }, stopCondition = { duration = durationAfter10, - condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")", + condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", } } }, @@ -169,7 +169,7 @@ do --setup route util action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } } @@ -375,7 +375,7 @@ do --setup route util action = { id = "Script", params = { - command = "pcall(Spearhead.Events.PublishRTB, \"" .. + command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" } } diff --git a/src/classes/stageClasses/GlobalStageManager.lua b/src/classes/stageClasses/GlobalStageManager.lua index ad232a3..71a636c 100644 --- a/src/classes/stageClasses/GlobalStageManager.lua +++ b/src/classes/stageClasses/GlobalStageManager.lua @@ -216,12 +216,15 @@ end ---@private function GlobalStageManager:UpdateDrawings(stageNumber) + self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber)) local drawings = self.database:getCustomDrawings() for _, drawing in pairs(drawings) do local startStage, stopStage = drawing:GetStartAndStop() if stageNumber >= startStage and stageNumber < stopStage then + self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber)) drawing:Draw() else + self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber)) drawing:Remove() end end diff --git a/src/classes/stageClasses/drawings/CustomDrawing.lua b/src/classes/stageClasses/drawings/CustomDrawing.lua index fa97160..df577c1 100644 --- a/src/classes/stageClasses/drawings/CustomDrawing.lua +++ b/src/classes/stageClasses/drawings/CustomDrawing.lua @@ -1,23 +1,27 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") local Util = require("classes.util.Util") +local Logger = require("classes.util.Logger") ---@class CustomDrawing ---@field private _id integer? ---@field private _drawingObject DrawingObject ---@field private _startingStage number ---@field private _removeAtStage number +---@field private _logger Logger local CustomDrawing = {} CustomDrawing.__index = CustomDrawing ---@param drawingObject DrawingObject ---@param id integer? +---@param loglevel LogLevel ---@return CustomDrawing -function CustomDrawing.New(drawingObject, id) +function CustomDrawing.New(drawingObject, id, loglevel) local self = setmetatable({}, CustomDrawing) self._drawingObject = drawingObject self._id = id + self._logger = Logger.new("CustomDrawing", loglevel) local name = drawingObject.name local split = Util.split_string(name or "", "_") @@ -28,6 +32,11 @@ function CustomDrawing.New(drawingObject, id) return self end +---@return string +function CustomDrawing:GetName() + return self._drawingObject.name +end + ---@return number start ---@return number stop function CustomDrawing:GetStartAndStop() @@ -35,11 +44,18 @@ function CustomDrawing:GetStartAndStop() end function CustomDrawing:Draw() - self._id = DrawingHelper.Draw(self._drawingObject) + self._logger:debug("Drawing custom drawing with ID " .. tostring(self._id)) + + if self._id ~= nil then + DrawingHelper.Remove(self._id) + end + + self._id = DrawingHelper.Draw(self._drawingObject, self._logger) end function CustomDrawing:Remove() if self._id ~= nil then + self._logger:debug("Removing custom drawing with ID " .. tostring(self._id)) DrawingHelper.Remove(self._id) self._id = nil end diff --git a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua index 99d6c93..fa2b179 100644 --- a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua +++ b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua @@ -1,25 +1,39 @@ - +local Util = require("classes.util.Util") ---@class DrawingHelper local DrawingHelper = {} DrawingHelper.__index = DrawingHelper + local customDrawingIdIncrementer = 4210 ---@param object DrawingObject +---@param logger Logger? ---@return integer? id -function DrawingHelper.Draw(object) +function DrawingHelper.Draw(object, logger) if object == nil then + if logger then + logger:warn("DrawingHelper.Draw called with nil object") + end return nil end local id = DrawingHelper.GetAndAddId() + + if logger then + logger:debug("Drawing object with ID " .. tostring(id) .. " and primitive type: " .. tostring(object.primitiveType)) + end + if(object.primitiveType == "Polygon") then - DrawingHelper.DrawPolygon(object--[[@as Polygon]], id) + DrawingHelper.DrawPolygon(object--[[@as Polygon]], id, logger) elseif(object.primitiveType == "Line") then - DrawingHelper.DrawLine(object--[[@as Line]], id) + DrawingHelper.DrawLine(object--[[@as Line]], id, logger) elseif(object.primitiveType == "TextBox") then DrawingHelper.DrawTextBox(object--[[@as TextBox]], id) + else + if logger then + logger:warn("Unknown primitive type: " .. tostring(object.primitiveType)) + end end return id @@ -36,16 +50,25 @@ end ---@param points Array ---@param fillColor table ---@param lineColor table -local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle) +---@param lineStyle LineType +---@param lineThickness number +---@param logger Logger? +local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness, logger) + + if lineThickness == nil or lineThickness <= 0 then + lineStyle = 0 + end local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. "," - for _, point in ipairs(points) do + + for _, point in pairs(points) do functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "}," end - functionString = functionString .. - "{ " .. lineColor[1] .. "," .. lineColor[2] .. "," .. lineColor[3] .. "," .. lineColor[4] .. "}, " .. - "{ " .. fillColor[1] .. "," .. fillColor[2] .. "," .. fillColor[3] .. "," .. fillColor[4] .. "}, " .. - lineStyle .. ")" + functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")" + + if logger then + logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString) + end ---@diagnostic disable-next-line: deprecated local f, err = loadstring(functionString) @@ -55,12 +78,21 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS env.error("Something failed when drawing complex drawing" .. err) end + if logger then + logger:debug("Drawing with fill color: " .. table.concat(fillColor, ",") .. " and line color: " .. table.concat(lineColor, ",") .. " and line style: " .. tostring(lineStyle) .. " and line thickness: " .. tostring(lineThickness)) + end + + trigger.action.setMarkupColorFill(drawID, fillColor) + trigger.action.setMarkupColor(drawID, lineColor) + trigger.action.setMarkupTypeLine(drawID, lineStyle) + end ---@private ---@param object Polygon ---@param id integer -function DrawingHelper.DrawPolygon(object, id) +---@param logger Logger? +function DrawingHelper.DrawPolygon(object, id, logger) if object == nil then return end @@ -75,11 +107,12 @@ function DrawingHelper.DrawPolygon(object, id) end ---@param oval Oval - local function DrawOval(oval) + ---@param logger Logger? + local function DrawOval(oval, logger) ---@type Array local points = {} local pointsNo = 30 - local angleStep = (2 * math.pi) / points + local angleStep = (2 * math.pi) / pointsNo local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString) local color = DrawingHelper.ColorToColorTable(oval.colorString) @@ -91,20 +124,37 @@ function DrawingHelper.DrawPolygon(object, id) local y = oval.mapY + (oval.r2 * math.sin(angle)) table.insert(points, { x = x, y = 0, z = y } ) end - MarkupToAll(7, id, points, fillColor, color, lineStyle) + MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness, logger) end ---@param free Free - local function DrawFree(free) + ---@param logger Logger? + local function DrawFree(free, logger) local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString) local color = DrawingHelper.ColorToColorTable(free.colorString) local lineStyle = DrawingHelper.ToLineStyleInteger(free.style) - local points = {} - for _, point in ipairs(free.points) do - table.insert(points, { x = point.x, y = 0, z = point.y } ) + local keys = {} + for k, _ in pairs(free.points) do + table.insert(keys, k) end - MarkupToAll(7, id, points, fillColor, color, lineStyle) + + table.sort(keys, function(a, b) return a < b end) + + local points = {} + for _, k in ipairs(keys) do + local point = free.points[k] + + local newPoint = { x = free.mapX + point.x, y = 0, z = free.mapY + point.y } + local firstPoint = points[1] + + if firstPoint == nil or newPoint.x ~= firstPoint.x or newPoint.z ~= firstPoint.z then + logger:debug("Drawing free polygon point " .. tostring(k) .. ": " .. tostring(free.points[k].x) .. ", " .. tostring(free.points[k].y)) + table.insert(points, newPoint) + end + end + + MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness, logger) end ---@param rect Rect @@ -116,38 +166,49 @@ function DrawingHelper.DrawPolygon(object, id) local pointA = { x = rect.mapX, y = 0, z = rect.mapY } local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height } trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true) + end ---@param arrow Arrow - local function DrawArrow(arrow) + ---@param logger Logger? + local function DrawArrow(arrow, logger) local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString) local color = DrawingHelper.ColorToColorTable(arrow.colorString) local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style) - local startPoint = { x = arrow.mapX, y = 0, z = arrow.mapY } + if logger then + logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length)) + end + + local endPoint = { x = arrow.mapX, y = 0, z = arrow.mapY } local rad = math.rad(arrow.angle or 0) local length = arrow.length or 100 - local endPoint = { x = arrow.mapX + length * math.cos(rad), y = 0, z = arrow.mapY + length * math.sin(rad) } + local startPoint = { x = arrow.mapX - length * math.sin(rad), y = 0, z = arrow.mapY + length * math.cos(rad) } trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true) end + if logger then + logger:debug("Drawing polygon with ID " .. tostring(id) .. " and polygon mode: " .. tostring(object.polygonMode)) + end + if object.polygonMode == "circle" then DrawCircle(object--[[@as Circle]]) elseif object.polygonMode == "oval" then - DrawOval(object--[[@as Oval]]) + DrawOval(object--[[@as Oval]], logger) elseif object.polygonMode == "free" then - DrawFree(object--[[@as Free]]) + DrawFree(object--[[@as Free]], logger) elseif object.polygonMode == "rect" then DrawRect(object--[[@as Rect]]) elseif object.polygonMode == "arrow" then - DrawArrow(object--[[@as Arrow]]) + DrawArrow(object--[[@as Arrow]], logger) end end ---@private ---@param object Line ---@param id integer -function DrawingHelper.DrawLine(object, id) +---@param logger Logger? +function DrawingHelper.DrawLine(object, id, logger) ---@type Array local points = {} @@ -158,7 +219,7 @@ function DrawingHelper.DrawLine(object, id) local color = DrawingHelper.ColorToColorTable(object.colorString) local lineStyle = DrawingHelper.ToLineStyleInteger(object.style) - MarkupToAll(1, id, points, color, color, lineStyle) + MarkupToAll(1, id, points, color, color, lineStyle, object.thickness, logger) end ---@private @@ -177,16 +238,14 @@ function DrawingHelper.Remove(id) trigger.action.removeMark(id) end - - ---@param hexStr string ---@return table function DrawingHelper.ColorToColorTable(hexStr) hexStr = hexStr:gsub("0x", "") - local a = tonumber(hexStr:sub(1, 2), 16) / 255 - local r = tonumber(hexStr:sub(3, 4), 16) / 255 - local g = tonumber(hexStr:sub(5, 6), 16) / 255 - local b = tonumber(hexStr:sub(7, 8), 16) / 255 + local r = tonumber(hexStr:sub(1, 2), 16) / 255 + local g = tonumber(hexStr:sub(3, 4), 16) / 255 + local b = tonumber(hexStr:sub(5, 6), 16) / 255 + local a = tonumber(hexStr:sub(7, 8), 16) / 255 return { r, g , b , a } end diff --git a/src/classes/util/Util.lua b/src/classes/util/Util.lua index 41dc0ff..f825790 100644 --- a/src/classes/util/Util.lua +++ b/src/classes/util/Util.lua @@ -297,6 +297,49 @@ do -- INIT UTIL return false end + ---@param points Array + ---@return Array + function UTIL.getConvexHull3d(points) + if #points == 0 then + return {} + end + + ---comment + ---@param a Vec3 + ---@param b Vec3 + ---@param c Vec3 + ---@return boolean + local function ccw(a, b, c) + return (b.z - a.z) * (c.x - a.x) > (b.x - a.x) * (c.z - a.z) + end + + table.sort(points, function(left, right) + return left.z < right.z + end) + + local hull = {} + -- lower hull + for _, point in pairs(points) do + while #hull >= 2 and not ccw(hull[#hull - 1], hull[#hull], point) do + table.remove(hull, #hull) + end + table.insert(hull, point) + end + + -- upper hull + local t = #hull + 1 + for i = #points, 1, -1 do + local point = points[i] + while #hull >= t and not ccw(hull[#hull - 1], hull[#hull], point) do + table.remove(hull, #hull) + end + table.insert(hull, point) + end + table.remove(hull, #hull) + return hull + end + + ---comment ---@param points Array points ---@return Array hullPoints -- 2.54.0 From d0e7f2aae491f692aead44f8453f07a1c3d4d590 Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Mon, 17 Aug 2026 10:12:24 +0000 Subject: [PATCH 09/16] fixed briefings faulty giving warnings (#19) Changed order of checking for mission briefings fixes #9Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/19 Co-authored-by: dutchie031 --- src/classes/spearhead_db.lua | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/classes/spearhead_db.lua b/src/classes/spearhead_db.lua index 653d1b2..87f3db2 100644 --- a/src/classes/spearhead_db.lua +++ b/src/classes/spearhead_db.lua @@ -350,18 +350,6 @@ function Database.New(Logger) end end - for _, missionZone in pairs(self._tables.MissionZones) do - if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then - MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing") - end - end - - for _, missionZone in pairs(self._tables.RandomMissionZones) do - if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then - MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing") - end - end - for _, farpZoneName in pairs(self._tables.AllFarpZones) do for _, airbase in pairs(world.getAirbases()) do if airbase:getDesc().category == Airbase.Category.HELIPAD then @@ -447,6 +435,21 @@ function Database.New(Logger) end end + -- Checks for missing briefings in mission zones and random mission zones + do + for _, missionZone in pairs(self._tables.MissionZones) do + if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then + MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing") + end + end + + for _, missionZone in pairs(self._tables.RandomMissionZones) do + if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then + MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing") + end + end + end + if missions == 0 then missions = 1 end self._logger:info("initiated the database with amount of zones: ") -- 2.54.0 From d8764c682451af9d8cbc80c667d7263551af20da Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Mon, 17 Aug 2026 10:13:25 +0000 Subject: [PATCH 10/16] Fix/briefing warnings (#20) Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/20 Co-authored-by: dutchie031 --- RELEASENOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e8e479e..787d794 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,6 +17,9 @@ PR #18 - Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue. PR #18 +- Fixed #9 + Changed order of checking mission briefings + PR #19 ## [0.12.0] 2026-06 -- 2.54.0 From a88520a413128596e746980c8cae6bf89e77f46e Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Mon, 17 Aug 2026 12:14:32 +0200 Subject: [PATCH 11/16] trigger beta branch --- src/main.lua | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/main.lua b/src/main.lua index 763ac58..fdc8f90 100644 --- a/src/main.lua +++ b/src/main.lua @@ -72,25 +72,3 @@ local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultL MissionEditorWarnings.WriteAll(missionEditorWarningsLogger) GlobalStageManager:printFullOverview() ---Check lines of code in directory per file: --- Get-ChildItem . -Include *.lua -Recurse | foreach {""+(Get-Content $_).Count + " => " + $_.name }; GCI . -Include *.lua* -Recurse | foreach{(GC $_).Count} | measure-object -sum | % Sum --- find . -name '*.lua' | xargs wc -l - ---- ==================== DEBUG ORDER OR ZONE VEC =========================== --- local zone = Spearhead.DcsUtil.getZoneByName("MISSIONSTAGE_99") - --- local count = Spearhead.Util.tableLength(zone.verts) - --- for i = 1, count - 1 do - --- local a = zone.verts[i] --- local b = zone.verts[i+1] - --- local color = {0,0,0,1} - --- color[i] = 1 - --- trigger.action.textToAll(-1, 46+i , { x= a.x, y = 0, z = a.z } , color, {0,0,0}, 24 , true , "" .. i ) --- trigger.action.lineToAll(-1 , 56+i , { x= a.x, y = 0, z = a.z } , { x = b.x, y = 0, z = b.z } , color , 1, true) - --- end -- 2.54.0 From c8329108dd7f9c45cd19bbdc9eb356202baf833c Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Mon, 17 Aug 2026 12:31:25 +0200 Subject: [PATCH 12/16] added link to the release --- .gitea/workflows/release-beta.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/release-beta.yml b/.gitea/workflows/release-beta.yml index 15120b7..0d446ea 100644 --- a/.gitea/workflows/release-beta.yml +++ b/.gitea/workflows/release-beta.yml @@ -100,4 +100,6 @@ jobs: embed-color: 3093247 embed-description: ${{ steps.release_notes.outputs.body }} + embed-url: + https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }} -- 2.54.0 From 63a1ea44db87732c133f81e2c026aef6bd5eedea Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Tue, 18 Aug 2026 12:50:12 +0000 Subject: [PATCH 13/16] Fix/ pre activation (#28) fixes: #8Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/28 Co-authored-by: dutchie031 --- RELEASENOTES.md | 4 + src/classes/configuration/GlobalConfig.lua | 14 +- src/classes/configuration/StageConfig.lua | 24 +++- src/classes/spearhead_db.lua | 2 +- .../stageClasses/SpecialZones/SupplyHub.lua | 17 ++- .../stageClasses/Stages/BaseStage/Stage.lua | 120 +++++++++++------- .../stageClasses/Stages/ExtraStage.lua | 10 +- .../stageClasses/drawings/CustomDrawing.lua | 68 +++++++++- .../drawings/helper/DrawingHelper.lua | 49 ++++++- .../stageClasses/helpers/BattleManager.lua | 38 ------ .../missions/BuildableMission.lua | 26 ++-- .../missions/RunwayStrikeMission.lua | 20 ++- .../stageClasses/missions/ZoneMission.lua | 2 +- src/classes/util/DcsUtil.lua | 69 ---------- src/classes/util/Logger.lua | 16 ++- 15 files changed, 284 insertions(+), 195 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 787d794..1ed1885 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,6 +20,10 @@ - Fixed #9 Changed order of checking mission briefings PR #19 +- Fixed Configuration defaulting to true for all booleans in StageConfig + PR #28 +- Fixed Pre-Activated stages not always drawing or pre-activating correctly. + PR #28 ## [0.12.0] 2026-06 diff --git a/src/classes/configuration/GlobalConfig.lua b/src/classes/configuration/GlobalConfig.lua index 4e5ba74..c794984 100644 --- a/src/classes/configuration/GlobalConfig.lua +++ b/src/classes/configuration/GlobalConfig.lua @@ -4,7 +4,8 @@ local briefingMessageTime = nil ---@class GlobalConfig ----@field private _briefingTime number ; +---@field private _briefingTime number +---@field private _debugEnabled boolean local GlobalConfig = {} GlobalConfig.__index = GlobalConfig; @@ -19,6 +20,13 @@ function GlobalConfig.New() if SpearheadConfig.briefingMessageDuration then self._briefingTime = SpearheadConfig.briefingMessageDuration end + + if SpearheadConfig.debugEnabled ~= nil then + self._debugEnabled = SpearheadConfig.debugEnabled + else + self._debugEnabled = false + end + end return self @@ -28,6 +36,10 @@ function GlobalConfig:getBriefingTime() return self._briefingTime or 60 end +function GlobalConfig:isDebugEnabled() + return self._debugEnabled or false +end + return GlobalConfig diff --git a/src/classes/configuration/StageConfig.lua b/src/classes/configuration/StageConfig.lua index 6b617f5..f6a3692 100644 --- a/src/classes/configuration/StageConfig.lua +++ b/src/classes/configuration/StageConfig.lua @@ -9,26 +9,38 @@ --- @field AmountPreactivateStage integer local StageConfig = {}; +local Logger = require("classes.util.Logger") +local _logger = Logger.new("StageConfig", Logger.LogLevel) + ---comment ---@return StageConfig function StageConfig:new() - if SpearheadConfig == nil then SpearheadConfig = {} end - if SpearheadConfig.StageConfig == nil then SpearheadConfig.StageConfig = {} end + if SpearheadConfig == nil then + _logger:warn("SpearheadConfig is nil, creating default SpearheadConfig") + SpearheadConfig = {} + end + + if SpearheadConfig.StageConfig == nil then + _logger:warn("SpearheadConfig.StageConfig is nil, creating default StageConfig") + SpearheadConfig.StageConfig = {} + end ---@type StageConfig local o = { - isEnabled = SpearheadConfig.StageConfig.enabled or true, - isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages or true, - isAutoStages = SpearheadConfig.StageConfig.autoStages or true, + isEnabled = SpearheadConfig.StageConfig.enabled ~= false, + isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages ~= false, + isAutoStages = SpearheadConfig.StageConfig.autoStages ~= false, startingStage = SpearheadConfig.StageConfig.startingStage or 1, maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10, - isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated or true, + isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated ~= false, AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1, } setmetatable(o, { __index = self }) + _logger:info("Successfully created StageConfig Object") + return o; end diff --git a/src/classes/spearhead_db.lua b/src/classes/spearhead_db.lua index 87f3db2..d36fcfc 100644 --- a/src/classes/spearhead_db.lua +++ b/src/classes/spearhead_db.lua @@ -231,7 +231,7 @@ function Database.New(Logger) for key, layer_object in pairs(layer.objects) do if Util.startswith(layer_object.name, "drawing_", true) then local object = layer_object --[[@as DrawingObject]] - local customDrawing = CustomDrawing.New(object, nil, self._logger.LogLevel) + local customDrawing = CustomDrawing.New(object) table.insert(self._tables.CustomDrawings, customDrawing) end end diff --git a/src/classes/stageClasses/SpecialZones/SupplyHub.lua b/src/classes/stageClasses/SpecialZones/SupplyHub.lua index 6b9b674..b01ee26 100644 --- a/src/classes/stageClasses/SpecialZones/SupplyHub.lua +++ b/src/classes/stageClasses/SpecialZones/SupplyHub.lua @@ -2,6 +2,8 @@ local Util = require("classes.util.Util") local DcsUtil = require("classes.util.DcsUtil") local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") +local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing") +local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") ---@class SupplyHub ---@field private _database Database @@ -12,7 +14,7 @@ local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionComma ---@field private _isCommmandAdded table ---@field private _missionCommandsHelper MissionCommandsHelper ---@field private _inZone table ----@field private _drawID number +---@field private _customDrawing CustomDrawing? ---@field private _cargoInUnits table> ---@field private _activeAtStart boolean ---@field private _active boolean @@ -30,6 +32,7 @@ function SupplyHub.new(database, logger, zoneName) self._database = database self._logger = logger self._zoneName = zoneName + self._customDrawing = nil local split = Util.split_string(zoneName, "_") if string.lower(split[2]) == "a" then @@ -72,13 +75,13 @@ function SupplyHub:Activate() self._logger:debug("Activating Supply Hub zone: " .. self._zoneName) local zone = DcsUtil.getZoneByName(self._zoneName) - if zone and self._drawID == nil then - ---@type DrawColor - local fillColor = { r=0, g=1, b=0, a=0.2 } - ---@type DrawColor - local lineColor = { r=0, g=1, b=0, a=1} + if zone and self._customDrawing == nil then + local fillColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 0.2 }) + local lineColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 1}) local lineStyle = 1 - self._drawID = DcsUtil.DrawZone(zone, lineColor, fillColor, lineStyle) + + self._customDrawing = CustomDrawing.FromZone(zone, lineColor, fillColor, lineStyle, 6) + self._customDrawing:Draw() end self._supplyUnitsTracker:RegisterHub(self) diff --git a/src/classes/stageClasses/Stages/BaseStage/Stage.lua b/src/classes/stageClasses/Stages/BaseStage/Stage.lua index 8444329..442e784 100644 --- a/src/classes/stageClasses/Stages/BaseStage/Stage.lua +++ b/src/classes/stageClasses/Stages/BaseStage/Stage.lua @@ -11,6 +11,8 @@ local StageBase = require("classes.stageClasses.SpecialZones.StageBase") local BlueSam = require("classes.stageClasses.SpecialZones.BlueSam") local Events = require("classes.spearhead_events") local GlobalCapManager = require("classes.capClasses.GlobalCapManager") +local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing") +local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") ---@alias StageColor ---| "RED" @@ -43,17 +45,15 @@ local GlobalCapManager = require("classes.capClasses.GlobalCapManager") --- @field zoneName string --- @field stageName string? --- @field stageNumber number +--- @field protected _currentStageState CurrentStageState --- @field protected _missionCommandsHelper MissionCommandsHelper ---- @field protected _isActive boolean ---- @field protected _isComplete boolean --- @field protected _missionPriority MissionPriority --- @field protected _database Database --- @field protected _db StageData --- @field protected _logger Logger ---- @field protected _preActivated boolean --- @field protected _activeStage integer --- @field protected _stageConfig StageConfig ---- @field protected _stageDrawingId integer +--- @field protected _customDrawing CustomDrawing --- @field protected _spawnedGroups Array --- @field protected _stageCompleteListeners Array --- @field protected CheckContinuousAsync fun(self:Stage, time:number) : number? @@ -63,13 +63,20 @@ local Stage = {} Stage.__index = Stage +---@enum CurrentStageState +Stage.CurrentStageState = { + INACTIVE = 0, + PREACTIVATED = 1, + ACTIVE = 2, + BLUE = 3 +} Stage.StageColors = { - INVISIBLE = { r=0, g=0, b=0, a=0 }, - RED_ACTIVE = { r=1, g=0, b=0, a=0.15 }, - RED_PREACTIVE = { r=1, g=0, b=0, a=0.10}, - BLUE = { r=0, g=0, b=1, a=0.10}, - GRAY = { r=80/255, g=80/255, b=80/255, a=0.10 } + INVISIBLE = { 0, 0, 0, 0 }, + RED_ACTIVE = { 1, 0, 0, 0.20 }, + RED_PREACTIVE = { 1, 0, 0, 0.05}, + BLUE = { 0, 0, 1, 0.10}, + GRAY = { 80/255, 80/255, 80/255, 0.10 } } ---comment @@ -83,12 +90,11 @@ Stage.StageColors = { function Stage:superNew(database, stageConfig, logger, initData, missionPriority, spawnManager) logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName) - + self._currentStageState = Stage.CurrentStageState.INACTIVE self.zoneName = initData.stageZoneName self.stageNumber = initData.stageNumber - self._isActive = false - self._isComplete = false self.stageName = initData.stageDisplayName + self._currentStageState = Stage.CurrentStageState.INACTIVE self.OnPostStageComplete = nil self.OnPostBlueActivated = nil @@ -110,13 +116,19 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority } self._activeStage = -99 - self._preActivated = false self._stageConfig = stageConfig or {} self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel) local zone = DcsUtil.getZoneByName(self.zoneName) if zone then - self._stageDrawingId = DcsUtil.DrawZone(zone, Stage.StageColors.INVISIBLE, Stage.StageColors.INVISIBLE, 4) + + local colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE) + local fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE) + + local customDrawing = CustomDrawing.FromZone(zone, colorString, fillColorString, 1, 5) + if customDrawing then + self._customDrawing = customDrawing + end end self._spawnedGroups = {} @@ -253,6 +265,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority end end + Events.AddStageNumberChangedListener(self) return self @@ -260,7 +273,7 @@ end ---@return boolean function Stage:IsComplete() - if self._isComplete == true then return true end + if self._currentStageState == Stage.CurrentStageState.BLUE then return true end for i, mission in pairs(self._db.sams) do local state = mission:getState() @@ -276,13 +289,13 @@ function Stage:IsComplete() end end - self._isComplete = true + self._currentStageState = Stage.CurrentStageState.BLUE return true end ---@return boolean function Stage:IsActive() - return self._isActive == true + return self._currentStageState == Stage.CurrentStageState.ACTIVE end ---comment @@ -367,10 +380,9 @@ function Stage:AddStageCompleteListener(listener) end ---Activates all SAMS, Airbase units etc all at once. ----@param draw boolean -function Stage:PreActivate(draw) - if self._preActivated == false then - self._preActivated = true +function Stage:PreActivate() + if self._currentStageState == Stage.CurrentStageState.INACTIVE then + self._currentStageState = Stage.CurrentStageState.PREACTIVATED for key, mission in pairs(self._db.sams) do if mission then mission:SpawnInactive() @@ -382,40 +394,60 @@ function Stage:PreActivate(draw) end end - if draw == true then - self:MarkStage(Stage.StageColors.RED_PREACTIVE) - end + self:MarkStage() end ----@param stageColor DrawColor -function Stage:MarkStage(stageColor) - local lineColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a } - local fillColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a } +function Stage:MarkStage() - if stageColor.a > 0 then - lineColor.a = 1 + self._logger:debug("Marking stage '" .. Util.toString(self.zoneName) .. "' with state: " .. self._currentStageState) + + if self._customDrawing then + self._customDrawing:Remove() end - if stageColor == Stage.StageColors.RED_PREACTIVE then - lineColor.a = 0 + if self._stageConfig.isDrawStagesEnabled == false then return end + if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == Stage.CurrentStageState.PREACTIVATED then + return end - if self._stageDrawingId and self._stageConfig.isDrawStagesEnabled == true then - DcsUtil.SetLineColor(self._stageDrawingId, lineColor) - DcsUtil.SetFillColor(self._stageDrawingId, fillColor) + if self._customDrawing then + self._customDrawing:UpdateDrawingObject(function(drawingObject) + local drawing = drawingObject --[[@as Polygon]] + if self._currentStageState == Stage.CurrentStageState.ACTIVE then + drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE) + drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE) + drawing.style = "dot dash" + elseif self._currentStageState == Stage.CurrentStageState.PREACTIVATED then + drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE) + drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE) + drawing.style = "no line" + elseif self._currentStageState == Stage.CurrentStageState.BLUE then + drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE) + drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE) + drawing.style = "two dash" + else + drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE) + drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE) + drawing.style = "no line" + end + + return drawing + end) + self._customDrawing:Draw() end + end function Stage:ActivateStage() - self._isActive = true; + self._currentStageState = Stage.CurrentStageState.ACTIVE - pcall(function() - self:MarkStage(Stage.StageColors.RED_ACTIVE) + self:PreActivate() + + pcall(function() + self:MarkStage() end) - self:PreActivate(false) - self._logger:debug("Activating Misc groups for zone. Count: " .. Util.tableLength(self._db.miscGroups)) for _, miscGroup in pairs(self._db.miscGroups) do miscGroup:Spawn() @@ -462,9 +494,9 @@ function Stage:OnStageNumberChanged(number) if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number) - self:PreActivate(true) + self:PreActivate() elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then - self:PreActivate(false) + self:PreActivate() end if number == self.stageNumber then @@ -559,7 +591,7 @@ end function Stage:ActivateBlueStage() self._logger:debug("Setting stage '" .. Util.toString(self.zoneName) .. "' to blue") - + self._currentStageState = Stage.CurrentStageState.BLUE for _, mission in pairs(self._db.missions) do mission:SpawnPersistedState() end @@ -575,7 +607,7 @@ function Stage:ActivateBlueStage() ---@param self Stage local ActivateBlueAsync = function(self) pcall(function() - self:MarkStage(Stage.StageColors.BLUE) + self:MarkStage() end) self:ActivateBlueGroups() diff --git a/src/classes/stageClasses/Stages/ExtraStage.lua b/src/classes/stageClasses/Stages/ExtraStage.lua index 2b62446..175dd9f 100644 --- a/src/classes/stageClasses/Stages/ExtraStage.lua +++ b/src/classes/stageClasses/Stages/ExtraStage.lua @@ -23,7 +23,7 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager) self.OnPostBlueActivated = function (selfStage) - selfStage:MarkStage(Stage.StageColors.GRAY) + selfStage:MarkStage() end self.OnPostStageComplete = function (selfStage) @@ -34,7 +34,7 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager) end ---comment ----@param self Stage +---@param self ExtraStage ---@param number integer function ExtraStage:OnStageNumberChanged(number) @@ -47,16 +47,16 @@ function ExtraStage:OnStageNumberChanged(number) if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number) - self:PreActivate(true) + self:PreActivate() elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then - self:PreActivate(false) + self:PreActivate() end if number == self.stageNumber then self:ActivateStage() end - if self._isComplete == true then + if self._currentStageState == Stage.CurrentStageState.BLUE then self:ActivateBlueStage() end diff --git a/src/classes/stageClasses/drawings/CustomDrawing.lua b/src/classes/stageClasses/drawings/CustomDrawing.lua index df577c1..55fc646 100644 --- a/src/classes/stageClasses/drawings/CustomDrawing.lua +++ b/src/classes/stageClasses/drawings/CustomDrawing.lua @@ -3,6 +3,9 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe local Util = require("classes.util.Util") local Logger = require("classes.util.Logger") + +local drawingLogger = Logger.new("CustomDrawing") + ---@class CustomDrawing ---@field private _id integer? ---@field private _drawingObject DrawingObject @@ -14,14 +17,12 @@ CustomDrawing.__index = CustomDrawing ---@param drawingObject DrawingObject ----@param id integer? ----@param loglevel LogLevel ---@return CustomDrawing -function CustomDrawing.New(drawingObject, id, loglevel) +function CustomDrawing.New(drawingObject) local self = setmetatable({}, CustomDrawing) self._drawingObject = drawingObject - self._id = id - self._logger = Logger.new("CustomDrawing", loglevel) + self._id = nil -- initiate ID as nil, when drawn it will be set to the ID returned by DrawingHelper.Draw + self._logger = drawingLogger local name = drawingObject.name local split = Util.split_string(name or "", "_") @@ -32,6 +33,58 @@ function CustomDrawing.New(drawingObject, id, loglevel) return self end +---@param zone SpearheadTriggerZone +---@param colorString string +---@param fillColorString string +---@param lineStyle LineType +---@param lineThickness number +---@return CustomDrawing? +function CustomDrawing.FromZone(zone, colorString, fillColorString, lineStyle, lineThickness) + if zone == nil then + drawingLogger:warn("CustomDrawing.FromZone called with nil zone") + return nil + end + + if zone.zone_type == "Cilinder" then + ---@type Circle + local drawingObject = { + mapX = zone.location.x, + mapY = zone.location.y, + radius = zone.radius, + name = zone.name .. "_drawing", + primitiveType = "Polygon", + polygonMode = "circle", + visible = true, + style = DrawingHelper.ToLineStyleString(lineStyle), + colorString = colorString, + fillColorString = fillColorString, + thickness = lineThickness, + } + + return CustomDrawing.New(drawingObject) + end + + if zone.zone_type == "Polygon" then + + ---@type Free + local drawingObject = { + mapX = 0, + mapY = 0, + points = zone.verts, + name = zone.name .. "_drawing", + primitiveType = "Polygon", + polygonMode = "free", + visible = true, + style = DrawingHelper.ToLineStyleString(lineStyle), + colorString = colorString, + fillColorString = fillColorString, + thickness = lineThickness, + } + + return CustomDrawing.New(drawingObject) + end +end + ---@return string function CustomDrawing:GetName() return self._drawingObject.name @@ -61,4 +114,9 @@ function CustomDrawing:Remove() end end +---@param updateFunc fun(drawingObject:DrawingObject):DrawingObject +function CustomDrawing:UpdateDrawingObject(updateFunc) + self._drawingObject = updateFunc(self._drawingObject) +end + return CustomDrawing \ No newline at end of file diff --git a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua index fa2b179..67fdd87 100644 --- a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua +++ b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua @@ -1,4 +1,14 @@ local Util = require("classes.util.Util") +local Logger = require("classes.util.Logger") +local GlobalConfig = require("classes.configuration.GlobalConfig") + +---@type LogLevel +local level = "INFO" +if GlobalConfig:isDebugEnabled() then + level = "DEBUG" +end + +local drawingLogger = Logger.new("DrawingHelper", level) ---@class DrawingHelper local DrawingHelper = {} @@ -149,7 +159,6 @@ function DrawingHelper.DrawPolygon(object, id, logger) local firstPoint = points[1] if firstPoint == nil or newPoint.x ~= firstPoint.x or newPoint.z ~= firstPoint.z then - logger:debug("Drawing free polygon point " .. tostring(k) .. ": " .. tostring(free.points[k].x) .. ", " .. tostring(free.points[k].y)) table.insert(points, newPoint) end end @@ -241,6 +250,13 @@ end ---@param hexStr string ---@return table function DrawingHelper.ColorToColorTable(hexStr) + + if hexStr == nil then + drawingLogger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}") + return { 0, 0, 0, 0 } + end + + hexStr = hexStr:gsub("0x", "") local r = tonumber(hexStr:sub(1, 2), 16) / 255 local g = tonumber(hexStr:sub(3, 4), 16) / 255 @@ -250,6 +266,17 @@ function DrawingHelper.ColorToColorTable(hexStr) return { r, g , b , a } end +---@param rgba table +---@return string +function DrawingHelper.ColorTableToColorString(rgba) + local r = string.format("%02X", math.floor(rgba[1] * 255)) + local g = string.format("%02X", math.floor(rgba[2] * 255)) + local b = string.format("%02X", math.floor(rgba[3] * 255)) + local a = string.format("%02X", math.floor(rgba[4] * 255)) + + return "0x" .. r .. g .. b .. a +end + ---@param lineStyle string function DrawingHelper.ToLineStyleInteger(lineStyle) lineStyle = lineStyle:lower() @@ -272,6 +299,26 @@ function DrawingHelper.ToLineStyleInteger(lineStyle) end end +function DrawingHelper.ToLineStyleString(lineStyle) + if lineStyle == 0 then + return "no line" + elseif lineStyle == 1 then + return "solid" + elseif lineStyle == 2 then + return "dashed" + elseif lineStyle == 3 then + return "dotted" + elseif lineStyle == 4 then + return "dot dash" + elseif lineStyle == 5 then + return "long dash" + elseif lineStyle == 6 then + return "two dash" + else + return "no line" + end +end + ---@class ARGB ---@field public a number ---@field public r number diff --git a/src/classes/stageClasses/helpers/BattleManager.lua b/src/classes/stageClasses/helpers/BattleManager.lua index 3dec4f3..dc855bb 100644 --- a/src/classes/stageClasses/helpers/BattleManager.lua +++ b/src/classes/stageClasses/helpers/BattleManager.lua @@ -137,10 +137,6 @@ function BattleManager:LetUnitsShoot(groups, targetGroups) } } - if debugDrawing == true then - self:DrawDebugLine(point, unit) - end - local controller = unit:getController() if controller then controller:setTask(shootTask) @@ -245,42 +241,8 @@ function BattleManager:GetRandomPoint(origin, groupHulls) if not hull then return nil end local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin) - if debugDrawing == true then - self:DrawDebugZone({ hull }) - end - return Util.randomFromList(shootPoints) --[[@as Vec2]] end -do --DEBUG - - ---@param unit Unit - ---@param target Vec2 - function BattleManager:DrawDebugLine(target, unit) - local color = {r = 1, g = 0, b = 0, a = 1} - if unit:getCoalition() == 2 then - color = {r = 0, g = 0, b = 1, a = 1} - end - - DcsUtil.DrawLine(unit:getPoint(), {x = target.x, y = 0, z = target.y}, color, 1) - end - - ---@param hulls Array> - function BattleManager:DrawDebugZone(hulls) - for _, drawHull in pairs(hulls) do - - ---@type SpearheadTriggerZone - local zone = { - name = "temp", - zone_type = "Polygon", - radius = 0, - verts = drawHull, - location = { x=drawHull[1].x, y=drawHull[1].y }, - } - - DcsUtil.DrawZone(zone, {r =0, g=0, b =1, a = 0.5} ,{r =0, g= 0, b =1, a = 0}, 1) - end - end -end --DEBUG return BattleManager diff --git a/src/classes/stageClasses/missions/BuildableMission.lua b/src/classes/stageClasses/missions/BuildableMission.lua index 4a7a8ac..d986d4c 100644 --- a/src/classes/stageClasses/missions/BuildableMission.lua +++ b/src/classes/stageClasses/missions/BuildableMission.lua @@ -5,6 +5,8 @@ local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTrac local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local GlobalConfig = require("classes.configuration.GlobalConfig") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") +local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing") +local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") ---@class BuildableMission : Mission, SupplyUnitEventListener ---@field private _requiredKilos number @@ -17,8 +19,8 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel ---@field private _supplyUnitsTracker SupplyUnitsTracker ---@field private _noLandingZone SpearheadTriggerZone? ---@field private _dropOffZone SpearheadTriggerZone? ----@field private _noLandingZoneId number ----@field private _dropOffZoneId number +---@field private _noLandingZoneDrawing CustomDrawing? +---@field private _dropOffZoneDrawing CustomDrawing? local BuildableMission = {} BuildableMission.__index = BuildableMission @@ -164,20 +166,20 @@ function BuildableMission:SpawnActive() return end - ---@type DrawColor - local lineColor = { r=230/255, g=93/255, b=49/255, a=1} - ---@type DrawColor - local fillColor = { r=230/255, g=93/255, b=49/255, a=0.2} - self._noLandingZoneId = DcsUtil.DrawZone(self._noLandingZone, lineColor, fillColor, 6) + local lineColor = DrawingHelper.ColorTableToColorString({ 230/255, 93/255, 49/255, 1}) + local fillColor = DrawingHelper.ColorTableToColorString({ 230/255, 93/255, 49/255, 0.2}) + self._noLandingZoneDrawing = CustomDrawing.FromZone(self._noLandingZone, lineColor, fillColor, 2, 6) + self._noLandingZoneDrawing:Draw() if self._dropOffZone == nil then self._logger:error("No drop off zone found for mission: " .. self.code) return end - local lineColor2 = { r=0, g=0, b=1, a=1} - local fillColor2 = { r=0, g=0, b=1, a=0} - self._dropOffZoneId = DcsUtil.DrawZone(self._dropOffZone, lineColor2, fillColor2, 6) + local lineColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 1}) + local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0}) + self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6) + self._dropOffZoneDrawing:Draw() ---@param selfA BuildableMission ---@param time number @@ -254,8 +256,8 @@ function BuildableMission:CheckCratesInZone() end if self._droppedKilos >= self._requiredKilos then - DcsUtil.RemoveMark(self._noLandingZoneId) - DcsUtil.RemoveMark(self._dropOffZoneId) + self._dropOffZoneDrawing:Remove() + self._noLandingZoneDrawing:Remove() self:NotifyMissionComplete() self._state = "COMPLETED" end diff --git a/src/classes/stageClasses/missions/RunwayStrikeMission.lua b/src/classes/stageClasses/missions/RunwayStrikeMission.lua index 27826bf..8e38e7f 100644 --- a/src/classes/stageClasses/missions/RunwayStrikeMission.lua +++ b/src/classes/stageClasses/missions/RunwayStrikeMission.lua @@ -1,6 +1,7 @@ local Mission = require("classes.stageClasses.missions.baseMissions.Mission") local Util = require("classes.util.Util") local DcsUtil = require("classes.util.DcsUtil") +local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") ---@class RunwayStrikeMission : Mission ---@field runwayBombingTracker RunwayBombingTracker @@ -153,10 +154,23 @@ function RunwayStrikeMission:Draw() if runwaySection.drawID == nil then local zone = self:SectionToSpearheadZone(runwaySection) + + ---@type Free + local drawObject = { + primitiveType = "Polygon", + polygonMode = "free", + mapX = 0, + mapY = 0, + points = zone.verts, + name = zone.name, + fillColorString = DrawingHelper.ColorTableToColorString(fillColor), + colorString = DrawingHelper.ColorTableToColorString(lineColor), + style = "solid", + thickness = 1, + visible = true, + } - local color = { r=0, g=1, b=0, a=0.5 } - - runwaySection.drawID = DcsUtil.DrawZone(zone, color, color, 5) + runwaySection.drawID = DrawingHelper.Draw(drawObject) else DcsUtil.SetFillColor(runwaySection.drawID, fillColor) DcsUtil.SetLineColor(runwaySection.drawID, lineColor) diff --git a/src/classes/stageClasses/missions/ZoneMission.lua b/src/classes/stageClasses/missions/ZoneMission.lua index 34c9c12..8be5f72 100644 --- a/src/classes/stageClasses/missions/ZoneMission.lua +++ b/src/classes/stageClasses/missions/ZoneMission.lua @@ -350,7 +350,7 @@ function ZoneMission:SpawnPersistedState() end end ----spawns the mission, but doesn't add +---spawns the mission, but doesn't add it to the mission commands. function ZoneMission:SpawnInactive() self._logger:info("PreActivating " .. self.name) diff --git a/src/classes/util/DcsUtil.lua b/src/classes/util/DcsUtil.lua index ce05cdd..1fbdacd 100644 --- a/src/classes/util/DcsUtil.lua +++ b/src/classes/util/DcsUtil.lua @@ -173,10 +173,6 @@ do -- INIT DCS_UTIL verts = enlargedPoints } - if SpearheadConfig and SpearheadConfig.debugEnabled == true then - DCS_UTIL.DrawZone(triggerZone, { r = 0, g = 1, b = 0, a = 1 }, { a = 0, r = 0, g = 1, b = 0 }, 1) - end - DCS_UTIL.__airbaseZonesByName[name] = triggerZone end end @@ -636,71 +632,6 @@ do -- INIT DCS_UTIL ---@field b number ---@field a number - local drawID = 400 - ---@param zone SpearheadTriggerZone - ---@param lineColor DrawColor - ---@param fillColor DrawColor - ---@param lineStyle LineType - ---@return number drawID - function DCS_UTIL.DrawZone(zone, lineColor, fillColor, lineStyle) - if lineStyle == nil then lineStyle = 4 end - drawID = drawID + 1 - if zone.zone_type == "Cilinder" then - trigger.action.circleToAll(-1, drawID, { x = zone.location.x, y = 0, z = zone.location.y }, zone.radius, - { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, lineStyle, true) - else - local functionString = "trigger.action.markupToAll(7, -1, " .. drawID .. "," - for _, vecpoint in pairs(zone.verts) do - functionString = functionString .. " { x=" .. vecpoint.x .. ", y=0,z=" .. vecpoint.y .. "}," - end - functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")" - - ---@diagnostic disable-next-line: deprecated - local f, err = loadstring(functionString) - if f then - f() - else - env.error("Something failed when drawing complex drawing" .. err) - end - end - local fillColorMapped = { - fillColor.r or 0, - fillColor.g or 0, - fillColor.b or 0, - fillColor.a or 0.5 - } - - local lineColorMapped = { - lineColor.r or 0, - lineColor.g or 0, - lineColor.b or 0, - lineColor.a or 1 - } - - trigger.action.setMarkupColorFill(drawID, fillColorMapped) - trigger.action.setMarkupColor(drawID, lineColorMapped) - - return drawID - end - - ---@param start Vec3 - ---@param finish Vec3 - ---@param lineColor DrawColor - ---@param lineStyle LineType - function DCS_UTIL.DrawLine(start, finish, lineColor, lineStyle) - if lineStyle == nil then lineStyle = 4 end - drawID = drawID + 1 - - local lineColorMapped = { - lineColor.r or 0, - lineColor.g or 0, - lineColor.b or 0, - lineColor.a or 1 - } - - trigger.action.lineToAll(-1, drawID, start, finish, lineColorMapped, lineStyle) - return drawID - end ---@param groupID number ---@param text string diff --git a/src/classes/util/Logger.lua b/src/classes/util/Logger.lua index 29fa232..5016cc7 100644 --- a/src/classes/util/Logger.lua +++ b/src/classes/util/Logger.lua @@ -1,5 +1,17 @@ local Util = require("classes.util.Util") +local SpearheadConfig = require("classes.configuration.GlobalConfig") + + +---@type LogLevel +local defaultLogLevel = "INFO" + +if SpearheadConfig then + if SpearheadConfig:isDebugEnabled() then + defaultLogLevel = "DEBUG" + end +end + --- @class Logger --- @field LoggerName string the name of the logger @@ -10,13 +22,13 @@ do ---comment ---@param logger_name any - ---@param logLevel LogLevel + ---@param logLevel LogLevel? override the default log level ---@return Logger function LOGGER.new(logger_name, logLevel) LOGGER.__index = LOGGER local self = setmetatable({}, LOGGER) self.LoggerName = logger_name or "(loggername not set)" - self.LogLevel = logLevel or "INFO" + self.LogLevel = logLevel or defaultLogLevel return self end -- 2.54.0 From 970f04944017e1d03f87e00f4e9011e7f87584d5 Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Tue, 18 Aug 2026 18:23:07 +0000 Subject: [PATCH 14/16] Updated max distance to be configurable (#29) Partly addresses #24 Does not fully fix thoughReviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/29 Co-authored-by: dutchie031 --- RELEASENOTES.md | 4 ++++ config.lua | 6 ++++++ src/classes/configuration/CapConfig.lua | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1ed1885..f8860f7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,10 @@ PR #28 - Fixed Pre-Activated stages not always drawing or pre-activating correctly. PR #28 +- Addressed #24 + CAP max commit range is now configurable in the config.lua file. + Issue remains open in order to apply further fine grained tuning. + PR #29 ## [0.12.0] 2026-06 diff --git a/config.lua b/config.lua index cc7da67..6dad272 100644 --- a/config.lua +++ b/config.lua @@ -29,6 +29,12 @@ SpearheadConfig = { -- unit: feet maxAlt = 28000, -- default 28000 + --The "maxDistance" that's set on the CAP tasking for the aircraft to commit to a target. + --This is the distance from the aircraft to the target that the aircraft will commit to engaging + --This is shared for CAP, Intercept and Sweep missions for now. + -- unit: nautical miles + maxCommitRange = 35, -- default 35 + -- DELAYS. -- Delays work as follow. -- When an aircraft lands alive and well it will be rearmed and ready to go. diff --git a/src/classes/configuration/CapConfig.lua b/src/classes/configuration/CapConfig.lua index 076cbe9..cd81a3f 100644 --- a/src/classes/configuration/CapConfig.lua +++ b/src/classes/configuration/CapConfig.lua @@ -34,7 +34,7 @@ function CapConfig.new() self._minDurationOnStation = 1200 self._maxDurationOnStation = 2700 - self._maxDeviationRange = 35 * 1852 -- in meters + self._maxDeviationRange = (tonumber(SpearheadConfig.CapConfig.maxCommitRange) or 35) * 1852 -- in meters self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600 self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600 self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800 -- 2.54.0 From 7c0c5e4a85fdc74b3f0772e9eb273dd9e5f5590c Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Thu, 20 Aug 2026 14:06:42 +0000 Subject: [PATCH 15/16] updated briefings to be shown during spawn (#31) fixes #26 Reviewed-on: https://git.dutchie031.com/Spearhead/spearhead/pulls/31 Co-authored-by: dutchie031 --- RELEASENOTES.md | 4 + src/classes/configuration/StageConfig.lua | 34 ++- src/classes/spearhead_events.lua | 23 ++ .../stageClasses/GlobalStageManager.lua | 23 +- .../stageClasses/SpecialZones/SupplyHub.lua | 4 +- .../stageClasses/Stages/BaseStage/Stage.lua | 2 +- .../helpers/MissionCommandsHelper.lua | 251 +++++++++--------- .../helpers/SupplyUnitsTracker.lua | 5 +- .../missions/BuildableMission.lua | 4 +- .../missions/baseMissions/Mission.lua | 2 +- src/main.lua | 4 +- 11 files changed, 210 insertions(+), 146 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f8860f7..0637357 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,10 @@ ### New Features +- Issue #26 + Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player. + PR #31 + ### Bug Fixes - Issue #11 diff --git a/src/classes/configuration/StageConfig.lua b/src/classes/configuration/StageConfig.lua index f6a3692..94590db 100644 --- a/src/classes/configuration/StageConfig.lua +++ b/src/classes/configuration/StageConfig.lua @@ -7,14 +7,16 @@ --- @field startingStage integer --- @field maxMissionsPerStage integer --- @field AmountPreactivateStage integer +--- @field briefingOnSpawnEnabled boolean local StageConfig = {}; +StageConfig.__index = StageConfig local Logger = require("classes.util.Logger") local _logger = Logger.new("StageConfig", Logger.LogLevel) ---comment ---@return StageConfig -function StageConfig:new() +local function new() if SpearheadConfig == nil then _logger:warn("SpearheadConfig is nil, creating default SpearheadConfig") @@ -26,22 +28,26 @@ function StageConfig:new() SpearheadConfig.StageConfig = {} end - ---@type StageConfig - local o = { - isEnabled = SpearheadConfig.StageConfig.enabled ~= false, - isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages ~= false, - isAutoStages = SpearheadConfig.StageConfig.autoStages ~= false, - startingStage = SpearheadConfig.StageConfig.startingStage or 1, - maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10, - isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated ~= false, - AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1, - } - - setmetatable(o, { __index = self }) + local self = setmetatable({}, StageConfig) + self.isEnabled = SpearheadConfig.StageConfig.enabled ~= false + self.isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages ~= false + self.isAutoStages = SpearheadConfig.StageConfig.autoStages ~= false + self.startingStage = SpearheadConfig.StageConfig.startingStage or 1 + self.maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10 + self.isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated ~= false + self.AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1 + self.briefingOnSpawnEnabled = SpearheadConfig.StageConfig.briefingOnSpawn ~= false _logger:info("Successfully created StageConfig Object") - return o; + return self; +end + +local config = new(); + +---@return StageConfig +function StageConfig:getInstance() + return config end return StageConfig \ No newline at end of file diff --git a/src/classes/spearhead_events.lua b/src/classes/spearhead_events.lua index 1ac612e..f3f19fd 100644 --- a/src/classes/spearhead_events.lua +++ b/src/classes/spearhead_events.lua @@ -50,6 +50,19 @@ do table.insert(OnStageNumberChangedHandlers, handler) end + ---@class OnStageNumberChangeCompleteListener + ---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer) + + local OnStageNumberChangeCompleteListeners = {} + ---@param listener OnStageNumberChangeCompleteListener + SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener) + if type(listener) ~= "table" or type(listener.OnStageNumberChangeComplete) ~= "function" then + warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number)") + return + end + table.insert(OnStageNumberChangeCompleteListeners, listener) + end + ---@param newStageNumber number SpearheadEvents.PublishStageNumberChanged = function(newStageNumber) pcall(function () @@ -71,6 +84,16 @@ do logError(err) end end + + for _, callable in pairs(OnStageNumberChangeCompleteListeners) do + local succ, err = pcall(function() + callable:OnStageNumberChangeComplete(newStageNumber) + end) + if err then + logError(err) + end + end + Logger.new("Events", "INFO"):info("Published stage number changed to: " .. tostring(newStageNumber)) end end diff --git a/src/classes/stageClasses/GlobalStageManager.lua b/src/classes/stageClasses/GlobalStageManager.lua index 71a636c..b8b372c 100644 --- a/src/classes/stageClasses/GlobalStageManager.lua +++ b/src/classes/stageClasses/GlobalStageManager.lua @@ -1,11 +1,13 @@ local Events = require("classes.spearhead_events") local Util = require("classes.util.Util") +local DcsUtil = require("classes.util.DcsUtil") local Logger = require("classes.util.Logger") local MissionEditorWarnings = require("classes.util.MissionEditorWarnings") local ExtraStage = require("classes.stageClasses.Stages.ExtraStage") local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage") local WaitingStage = require("classes.stageClasses.Stages.WaitingStage") +local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local StagesByName = {} @@ -20,10 +22,11 @@ local WaitingStagesByIndex = {} local currentStage = -99 ----@class GlobalStageManager : StageCompleteListener, OnStageChangedListener +---@class GlobalStageManager : StageCompleteListener, OnStageChangedListener, OnStageNumberChangeCompleteListener ---@field private database Database ---@field private logger Logger ---@field private stageConfig StageConfig +---@field private _missionCommandsHelper MissionCommandsHelper local GlobalStageManager = {} GlobalStageManager.__index = GlobalStageManager @@ -41,6 +44,7 @@ function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnMa local self = setmetatable({}, GlobalStageManager) self.database = database self.stageConfig = stageConfig + self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self.logger = logger if stageConfig.isAutoStages ~= true then @@ -214,6 +218,23 @@ function GlobalStageManager:OnStageNumberChanged(stageNumber) self:UpdateDrawings(stageNumber) end +function GlobalStageManager:OnStageNumberChangeComplete(stageNumber) + self.logger:debug("Stage number change complete to: " .. tostring(stageNumber)) + + local groups = {} + + for _, player in pairs(DcsUtil.getAllPlayerUnits()) do + local group = player:getGroup() + if group then + groups[group:getID()] = group + end + end + + for _, group in pairs(groups) do + self._missionCommandsHelper:OverviewToGroup(group:getID()) + end +end + ---@private function GlobalStageManager:UpdateDrawings(stageNumber) self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber)) diff --git a/src/classes/stageClasses/SpecialZones/SupplyHub.lua b/src/classes/stageClasses/SpecialZones/SupplyHub.lua index b01ee26..c05aba5 100644 --- a/src/classes/stageClasses/SpecialZones/SupplyHub.lua +++ b/src/classes/stageClasses/SpecialZones/SupplyHub.lua @@ -43,9 +43,9 @@ function SupplyHub.new(database, logger, zoneName) self._zone = DcsUtil.getZoneByName(zoneName) - self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logger.LogLevel) + self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() self._inZone = {} - self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel) + self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._logger:debug("Creating Supply Hub zone: " .. self._zoneName) diff --git a/src/classes/stageClasses/Stages/BaseStage/Stage.lua b/src/classes/stageClasses/Stages/BaseStage/Stage.lua index 442e784..9ce9183 100644 --- a/src/classes/stageClasses/Stages/BaseStage/Stage.lua +++ b/src/classes/stageClasses/Stages/BaseStage/Stage.lua @@ -117,7 +117,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority self._activeStage = -99 self._stageConfig = stageConfig or {} - self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel) + self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() local zone = DcsUtil.getZoneByName(self.zoneName) if zone then diff --git a/src/classes/stageClasses/helpers/MissionCommandsHelper.lua b/src/classes/stageClasses/helpers/MissionCommandsHelper.lua index 1328af3..b9f33fb 100644 --- a/src/classes/stageClasses/helpers/MissionCommandsHelper.lua +++ b/src/classes/stageClasses/helpers/MissionCommandsHelper.lua @@ -4,6 +4,7 @@ local Logger = require("classes.util.Logger") local SpearheadEvents = require("classes.spearhead_events") local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") +local StageConfig = require("classes.configuration.StageConfig") ---@class MissionCommandsHelper @@ -16,6 +17,7 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel ---@field private _stageBriefings table @table of stage briefings by stage name ---@field private _logger Logger @logger instance for logging ---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance +---@field private _stageConfig StageConfig local MissionCommandsHelper = {} MissionCommandsHelper.__index = MissionCommandsHelper @@ -23,8 +25,8 @@ MissionCommandsHelper.__index = MissionCommandsHelper ---@param groupPos Vec2 local function sortMissions(list, groupPos) table.sort(list, function(a, b) - local distA = Util.VectorDistance2d(groupPos, a.location or {x=0, y=0}) - local distB = Util.VectorDistance2d(groupPos, b.location or {x=0, y=0}) + local distA = Util.VectorDistance2d(groupPos, a.location or { x = 0, y = 0 }) + local distB = Util.VectorDistance2d(groupPos, b.location or { x = 0, y = 0 }) return distA < distB; end) end @@ -34,12 +36,11 @@ local id = 0 local instance = nil ---@return MissionCommandsHelper ----@param logLevel string @log level for the logger -function MissionCommandsHelper.getOrCreate(logLevel) +function MissionCommandsHelper.getOrCreate() if instance == nil then instance = setmetatable({}, MissionCommandsHelper) - instance._logger = Logger.new("MissionCommandsHelper", logLevel) + instance._logger = Logger.new("MissionCommandsHelper") instance._logger:info("Creating MissionCommandsHelper instance") @@ -50,7 +51,9 @@ function MissionCommandsHelper.getOrCreate(logLevel) instance.lastUpdate = 0 instance._stageBriefings = {} - instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logLevel) + instance._stageConfig = StageConfig:getInstance() + + instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() instance._supplyUnitsTracker:AddOnSupplyUnitEventListener( { @@ -95,7 +98,6 @@ function MissionCommandsHelper.getOrCreate(logLevel) timer.scheduleFunction(instance.updateContinuous, instance, timer.getTime() + 5) SpearheadEvents.AddOnPlayerEnterUnitListener(instance) - end return instance @@ -124,12 +126,16 @@ function MissionCommandsHelper:RemoveMissionToCommands(mission) self.updateNeeded = true end - ---@param unit Unit function MissionCommandsHelper:OnPlayerEntersUnit(unit) if unit then local group = unit:getGroup() - if group then self:updateCommandsForGroup(group:getID()) end + if group then + self:updateCommandsForGroup(group:getID()) + if self._stageConfig.briefingOnSpawnEnabled == true then + self:OverviewToGroup(group:getID()) + end + end end end @@ -164,111 +170,116 @@ local pinMissionCommand = function(args) end end +---@param groupID integer +function MissionCommandsHelper:OverviewToGroup(groupID) + local text = "Missions Overview\n\n" + + local group = DcsUtil.GetPlayerGroupByGroupID(groupID) + ---@type Vec2 + local groupPos = { x = 0, y = 0 } + if group then + local pos = group:getUnit(1):getPosition().p + groupPos = { x = pos.x, y = pos.z } + end + + ---@param mission Mission + ---@return string + local function formatLine(mission) + local distanceText = "?" + if group then + local lead = group:getUnit(1) + if lead and lead:isExist() == true then + local pos = lead:getPoint() + local Vec2Pos = { x = pos.x, y = pos.z } + local distance = Util.VectorDistance2d(Vec2Pos, mission.location) / 1852 + distanceText = string.format("~%d", math.floor(distance)) + end + end + + return string.format("[%s]\t%s \t%s \t%s %% \t%s nM\n", mission.code, mission.missionTypeDisplay, mission.name, + mission:PercentageComplete(), distanceText) + end + + for _, briefing in pairs(self._stageBriefings) do + text = text .. briefing .. "\n\n" + end + + ---Primary missions + text = text .. "Primary Missions\n" + + ---@type Array + local primaryMissions = {} + 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 + table.insert(primaryMissions, mission) + end + end + end + + sortMissions(primaryMissions, groupPos) + + for _, mission in pairs(primaryMissions) do + text = text .. formatLine(mission) + end + + ---Secondary missions + text = text .. "\nSecondary Missions\n" + + ---@type Array + local secondaryMissions = {} + 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 + table.insert(secondaryMissions, mission) + end + end + end + + sortMissions(secondaryMissions, groupPos) + for _, mission in pairs(secondaryMissions) do + text = text .. formatLine(mission) + end + + + trigger.action.outTextForGroup(groupID, text, 20, true) +end + ---@private function MissionCommandsHelper:AddOverviewCommand(groupID) - local MissionsOverviewToGroup = function (id) - - local text = "Missions Overview\n\n" + ---@class OverviewToGroupCommandArgs + ---@field self MissionCommandsHelper @the MissionCommandsHelper instance + ---@field groupId integer @the group ID of the player requesting the overview - local group = DcsUtil.GetPlayerGroupByGroupID(id) - ---@type Vec2 - local groupPos = { x=0, y=0 } - if group then - local pos = group:getUnit(1):getPosition().p - groupPos = { x= pos.x, y=pos.z } - end - - ---comment - ---@param mission Mission - ---@return string - local function formatLine(mission) - - local distanceText = "?" - if group then - local lead = group:getUnit(1) - if lead and lead:isExist() == true then - local pos = lead:getPoint() - local Vec2Pos = { x= pos.x, y=pos.z } - local distance = Util.VectorDistance2d(Vec2Pos, mission.location) / 1852 - distanceText = string.format("~%d", math.floor(distance)) - end - end - - return string.format("[%s]\t%s \t%s \t%s %% \t%s nM\n", mission.code, mission.missionTypeDisplay, mission.name, mission:PercentageComplete(), distanceText) - end - - for _, briefing in pairs(self._stageBriefings) do - text = text .. briefing .. "\n\n" - end - - - - - ---Primary missions - text = text .. "Primary Missions\n" - - ---@type Array - local primaryMissions = {} - 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 - table.insert(primaryMissions, mission) - end - end - end - - sortMissions(primaryMissions, groupPos) - - for _, mission in pairs(primaryMissions) do - text = text .. formatLine(mission) - end - - ---Secondary missions - text = text .. "\nSecondary Missions\n" - - ---@type Array - local secondaryMissions = {} - 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 - table.insert(secondaryMissions, mission) - end - end - end - - sortMissions(secondaryMissions, groupPos) - for _, mission in pairs(secondaryMissions) do - text = text .. formatLine(mission) - end - - - trigger.action.outTextForGroup(id, text, 20, true) + ---@param args OverviewToGroupCommandArgs + local MissionOverViewToGroup = function(args) + args.self:OverviewToGroup(args.groupId) end - missionCommands.removeItemForGroup(groupID, { "Overview" } ) - missionCommands.addCommandForGroup(groupID, "Overview", nil, MissionsOverviewToGroup, groupID) + ---@type OverviewToGroupCommandArgs + local overviewToGroupCommandArgs = { self = self, groupId = groupID } + + missionCommands.removeItemForGroup(groupID, { "Overview" }) + missionCommands.addCommandForGroup(groupID, "Overview", nil, MissionOverViewToGroup, overviewToGroupCommandArgs) end ---@private ---@param groupID number function MissionCommandsHelper:AddPinnedMission(groupID) - local pinndedMission = self.pinnedByGroup[tostring(groupID)] missionCommands.removeItemForGroup(groupID, { "Pinned Mission" }) if pinndedMission and self.enabledByCode[tostring(pinndedMission.code)] == true then - missionCommands.addCommandForGroup(groupID, "Pinned Mission", nil, missionBriefingRequested, { groupId = groupID, mission = pinndedMission }) + missionCommands.addCommandForGroup(groupID, "Pinned Mission", nil, missionBriefingRequested, + { groupId = groupID, mission = pinndedMission }) end - end ---@param groupID number function MissionCommandsHelper:updateCommandsForGroup(groupID) - self._logger:debug("Updating commands for group: " .. tostring(groupID)) self:AddPinnedMission(groupID) @@ -286,15 +297,14 @@ function MissionCommandsHelper:updateCommandsForGroup(groupID) trigger.action.outTextForGroup(id, "clearing...", 1, true) end - missionCommands.removeItemForGroup(groupID, { "Clear View" } ) + missionCommands.removeItemForGroup(groupID, { "Clear View" }) missionCommands.addCommandForGroup(groupID, "Clear View", nil, clearView, groupID) - missionCommands.removeItemForGroup(groupID, { "Refresh Missions" } ) + missionCommands.removeItemForGroup(groupID, { "Refresh Missions" }) missionCommands.addCommandForGroup(groupID, "Refresh Missions", nil, function(refresh_mission_id) self._logger:debug("Manual refresh of missions for group: " .. tostring(refresh_mission_id)) self:updateCommandsForGroup(refresh_mission_id) end, groupID) - end local folderNames = { @@ -317,17 +327,16 @@ function MissionCommandsHelper:PinMission(mission, groupID) end function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID) - local perFolder = 9 local group = DcsUtil.GetPlayerGroupByGroupID(groupID) ---@type Vec2 - local groupPos = { x=0, y=0 } + local groupPos = { x = 0, y = 0 } if group then local pos = group:getUnit(1):getPosition().p - groupPos = { x= pos.x, y=pos.z } + groupPos = { x = pos.x, y = pos.z } end - + do --- primary missions local count = 0 local path = { [1] = folderNames.primary } @@ -353,7 +362,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID) else local name = "Next Menu ..." missionCommands.addSubMenuForGroup(groupID, name, path) - path[#path+1] = name + path[#path + 1] = name count = 0 end end @@ -382,7 +391,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID) else local name = "Next Menu ..." missionCommands.addSubMenuForGroup(groupID, name, path) - path[#path+1] = name + path[#path + 1] = name count = 0 end end @@ -395,28 +404,28 @@ end ---@param path Array ---@param mission Mission function MissionCommandsHelper:addMissionCommands(groupId, path, mission) - if path then - local group = DcsUtil.GetPlayerGroupByGroupID(groupId) local distance = "[?]" if group then local lead = group:getUnit(1) if lead and lead:isExist() == true then local pos = lead:getPoint() - local Vec2Pos = { x= pos.x, y=pos.z } + local Vec2Pos = { x = pos.x, y = pos.z } local dist = Util.VectorDistance2d(Vec2Pos, mission.location) / 1852 distance = "[" .. string.format("~%dnM", math.floor(dist)) .. "]" end end - local missionFolderName = "[" .. mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )" + local missionFolderName = "[" .. + mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )" missionCommands.addSubMenuForGroup(groupId, missionFolderName, path) table.insert(path, missionFolderName) ---@type MissionBriefingRequestedArgs local missionBriefingRequestedArgs = { groupId = groupId, mission = mission } - missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested,missionBriefingRequestedArgs) + missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested, + missionBriefingRequestedArgs) ---@type PinMissionCommandArgs local pinMissionCommandArgs = { self = self, groupId = groupId, mission = mission } @@ -427,7 +436,6 @@ end ---@private ---@param groupID integer function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID) - if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupID) ~= true then return end self._logger:debug("Adding supply hub commands for group: " .. tostring(groupID)) @@ -458,28 +466,32 @@ function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID) local path = { [1] = folderNames.supplyHub } ---@type LoadCargoCommandParams - local farpParams1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } + local farpParams1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = + self._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Load FARP Crate (1000)", path, loadCargoCommand, farpParams1000) ---@type LoadCargoCommandParams - local farpParams2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } + local farpParams2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = + self._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Load FARP Crate (2000)", path, loadCargoCommand, farpParams2000) ---@type LoadCargoCommandParams - local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } + local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = + self._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Load SAM Crate (1000)", path, loadCargoCommand, samParms1000) ---@type LoadCargoCommandParams - local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } + local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = + self._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Load SAM Crate (2000)", path, loadCargoCommand, samParms2000) ---@type LoadCargoCommandParams - local airbaseParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "AIRBASE_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } + local airbaseParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "AIRBASE_CRATE_2000", supplyUnitsTracker = + self._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Airbase Crate (2000)", path, loadCargoCommand, airbaseParms2000) end function MissionCommandsHelper:AddCargoCommands(groupID) - local group = DcsUtil.GetPlayerGroupByGroupID(groupID) if group == nil then return end @@ -509,20 +521,19 @@ function MissionCommandsHelper:AddCargoCommands(groupID) for i = 1, amount do local path = { [1] = folderNames.cargo } ---@type UnloadCargoCommandParams - local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self } - missionCommands.addCommandForGroup(groupID, "Unload " .. cargoConfig.displayName, path, unloadCargoCommand, params) + local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self + ._supplyUnitsTracker, commandHelper = self } + missionCommands.addCommandForGroup(groupID, "Unload " .. cargoConfig.displayName, path, + unloadCargoCommand, params) end end end end end - - ---@private ---@param groupId integer function MissionCommandsHelper:addMissionFolders(groupId) - missionCommands.addSubMenuForGroup(groupId, folderNames.primary) missionCommands.addSubMenuForGroup(groupId, folderNames.secondary) diff --git a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua index d98d2e0..e83a582 100644 --- a/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua +++ b/src/classes/stageClasses/helpers/SupplyUnitsTracker.lua @@ -26,13 +26,12 @@ SupplyUnitsTracker.__index = SupplyUnitsTracker local singleton = nil ---comment ----@param logLevel LogLevel ---@return SupplyUnitsTracker -function SupplyUnitsTracker.getOrCreate(logLevel) +function SupplyUnitsTracker.getOrCreate() if singleton == nil then singleton = setmetatable({}, SupplyUnitsTracker) - singleton._logger = Logger.new("SupplyUnitsTracker", logLevel) + singleton._logger = Logger.new("SupplyUnitsTracker") singleton._unitPositions = {} singleton._cargoInUnits = {} singleton._supplyUnitsByName = {} diff --git a/src/classes/stageClasses/missions/BuildableMission.lua b/src/classes/stageClasses/missions/BuildableMission.lua index d986d4c..7707c0c 100644 --- a/src/classes/stageClasses/missions/BuildableMission.lua +++ b/src/classes/stageClasses/missions/BuildableMission.lua @@ -78,7 +78,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi self._onCrateDroppedOfListeners = {} self._completeListeners = {} self._markIDsPerGroup = {} - self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logger.LogLevel) + self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() self._state = "NEW" @@ -89,7 +89,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi self.priority = "secondary" - self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(self._logger.LogLevel) + self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._crateType = requiredCrateType diff --git a/src/classes/stageClasses/missions/baseMissions/Mission.lua b/src/classes/stageClasses/missions/baseMissions/Mission.lua index 22e183c..54b1afc 100644 --- a/src/classes/stageClasses/missions/baseMissions/Mission.lua +++ b/src/classes/stageClasses/missions/baseMissions/Mission.lua @@ -52,7 +52,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief self.location = database:GetLocationForMissionZone(zoneName) self.missionTypeDisplay = self.missionType - self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel) + self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() return true, "success" end diff --git a/src/main.lua b/src/main.lua index fdc8f90..4d361f1 100644 --- a/src/main.lua +++ b/src/main.lua @@ -28,10 +28,10 @@ SpearheadEvents.Init(defaultLogLevel) local dbLogger = Logger.new("database", defaultLogLevel) local standardLogger = Logger.new("", defaultLogLevel) local databaseManager = Database.New(dbLogger) -MissionCommandsHelper.getOrCreate(defaultLogLevel) -- initiate +MissionCommandsHelper.getOrCreate() -- initiate local capConfig = CapConfig:new(); -local stageConfig = StageConfig:new(); +local stageConfig = StageConfig:getInstance(); local startingStage = stageConfig.startingStage or 1 if SpearheadConfig and SpearheadConfig.Persistence and SpearheadConfig.Persistence.enabled == true then -- 2.54.0 From 952daa2897120054b850dce91f102400ec2b8a0d Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Thu, 20 Aug 2026 16:58:43 +0200 Subject: [PATCH 16/16] Cutting a release --- .gitea/workflows/release.yml | 15 +++++++++++++++ RELEASENOTES.md | 10 ++++++++++ .../drawings/helper/DrawingHelper.lua | 5 +++++ version.txt | 2 +- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 6e07988..21c24da 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -84,3 +84,18 @@ jobs: env: NODE_OPTIONS: '--experimental-fetch' + - name: Announce Release + uses: tsickert/discord-webhook@v7.0.0 + with: + webhook-url: ${{ secrets.WEBHOOK_URL }} + content: "New Beta Release" + username: "Spearhead Release Bot" + #TODO: avatar-url spearhead avatar + thread-id: 1538520596802175037 + embed-title: "Spearhead Release" + embed-color: 3093247 + embed-description: + ${{ steps.release_notes.outputs.body }} + embed-url: + https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }} + diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0637357..effc01c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,16 @@ ### New Features +### Bug Fixes + +## [0.13.0] 2026-08 + +A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea.
+ +### Breaking Changes + +### New Features + - Issue #26 Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player. PR #31 diff --git a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua index 67fdd87..8432890 100644 --- a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua +++ b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua @@ -269,6 +269,11 @@ end ---@param rgba table ---@return string function DrawingHelper.ColorTableToColorString(rgba) + if rgba == nil or #rgba < 4 or rgba[1] == nil or rgba[2] == nil or rgba[3] == nil or rgba[4] == nil then + drawingLogger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'") + return "0x00000000" + end + local r = string.format("%02X", math.floor(rgba[1] * 255)) local g = string.format("%02X", math.floor(rgba[2] * 255)) local b = string.format("%02X", math.floor(rgba[3] * 255)) diff --git a/version.txt b/version.txt index aac2dac..51de330 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.12.1 \ No newline at end of file +0.13.0 \ No newline at end of file -- 2.54.0