Compare commits
7
Commits
v0.13.0
...
cb373fbb59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb373fbb59 | ||
|
|
a1e36aa5d7 | ||
|
|
b9c5c7df8f | ||
|
|
1edcbb3666 | ||
|
|
4b30cc3a32 | ||
|
|
163e59443b | ||
|
|
bb9144e3a0 |
@@ -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<<EOF" >> "$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'
|
||||||
|
|
||||||
@@ -4,3 +4,4 @@
|
|||||||
/dist
|
/dist
|
||||||
|
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
**\settings.json
|
||||||
Vendored
-25
@@ -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",
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,13 @@
|
|||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Issue #11 <br>
|
||||||
|
PR #17 <br>
|
||||||
|
Supply crate spawning now checks for free space and will not spawn if the area is too crowded. <br>
|
||||||
|
Additionally different units will spawn in different areas depending on loading side.
|
||||||
|
- PR #15 <br>
|
||||||
|
Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone.
|
||||||
|
|
||||||
|
|
||||||
## [0.12.1] 2026-07
|
## [0.12.1] 2026-07
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ SpearheadConfig = {
|
|||||||
|
|
||||||
--- The time briefings should be displayed by default.
|
--- 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
|
--- 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 = {
|
CapConfig = {
|
||||||
--quickly enable of disable the entire CAP Logic
|
--quickly enable of disable the entire CAP Logic
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function GlobalConfig.New()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GlobalConfig:getBriefingTime()
|
function GlobalConfig:getBriefingTime()
|
||||||
return self._briefingTime or 30
|
return self._briefingTime or 60
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
---@class MaxLoadConfig
|
|
||||||
---@field maxInternalLoad number
|
|
||||||
|
|
||||||
---@type table<string, MaxLoadConfig>
|
|
||||||
local MaxLoadConfig = {
|
|
||||||
["Mi-8MT"] = {
|
|
||||||
maxInternalLoad = 4000,
|
|
||||||
},
|
|
||||||
["CH-47Fbl1"] = {
|
|
||||||
maxInternalLoad = 10000
|
|
||||||
},
|
|
||||||
["Mi-24P"] = {
|
|
||||||
maxInternalLoad = 2000
|
|
||||||
},
|
|
||||||
["UH-1H"] = {
|
|
||||||
maxInternalLoad = 2000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MaxLoadConfig
|
|
||||||
@@ -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 updateContinuous fun(self: MissionCommandsHelper, time: number): number @function to update commands continuously
|
||||||
---@field pinnedByGroup table<string, Mission> @table of pinned missions by group ID
|
---@field pinnedByGroup table<string, Mission> @table of pinned missions by group ID
|
||||||
---@field private _stageBriefings table<string, string> @table of stage briefings by stage name
|
---@field private _stageBriefings table<string, string> @table of stage briefings by stage name
|
||||||
---@field private _supplyHubGroups table<string, boolean> @table of supply hub groups by their ID
|
|
||||||
---@field private _logger Logger @logger instance for logging
|
---@field private _logger Logger @logger instance for logging
|
||||||
---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance
|
---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance
|
||||||
local MissionCommandsHelper = {}
|
local MissionCommandsHelper = {}
|
||||||
@@ -49,11 +48,28 @@ function MissionCommandsHelper.getOrCreate(logLevel)
|
|||||||
instance.updateNeeded = false
|
instance.updateNeeded = false
|
||||||
instance.pinnedByGroup = {}
|
instance.pinnedByGroup = {}
|
||||||
instance.lastUpdate = 0
|
instance.lastUpdate = 0
|
||||||
instance._supplyHubGroups = {}
|
|
||||||
instance._stageBriefings = {}
|
instance._stageBriefings = {}
|
||||||
|
|
||||||
instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logLevel)
|
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
|
---comment
|
||||||
---@param selfA MissionCommandsHelper
|
---@param selfA MissionCommandsHelper
|
||||||
---@param time number
|
---@param time number
|
||||||
@@ -108,32 +124,6 @@ function MissionCommandsHelper:RemoveMissionToCommands(mission)
|
|||||||
self.updateNeeded = true
|
self.updateNeeded = true
|
||||||
end
|
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
|
---@param unit Unit
|
||||||
function MissionCommandsHelper:OnPlayerEntersUnit(unit)
|
function MissionCommandsHelper:OnPlayerEntersUnit(unit)
|
||||||
@@ -438,7 +428,7 @@ end
|
|||||||
---@param groupID integer
|
---@param groupID integer
|
||||||
function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID)
|
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))
|
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.primary)
|
||||||
missionCommands.addSubMenuForGroup(groupId, folderNames.secondary)
|
missionCommands.addSubMenuForGroup(groupId, folderNames.secondary)
|
||||||
|
|
||||||
if self._supplyHubGroups[tostring(groupId)] == true then
|
if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupId) == true then
|
||||||
self._logger:debug("Adding supply hub commands folder for group: " .. tostring(groupId))
|
|
||||||
missionCommands.addSubMenuForGroup(groupId, folderNames.supplyHub)
|
missionCommands.addSubMenuForGroup(groupId, folderNames.supplyHub)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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<DropZoneSlice>
|
||||||
|
|
||||||
|
---@type table<string, SupplyLoadConfig>
|
||||||
|
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 = 60, 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
|
||||||
@@ -3,7 +3,7 @@ local Util = require("classes.util.Util")
|
|||||||
local DcsUtil = require("classes.util.DcsUtil")
|
local DcsUtil = require("classes.util.DcsUtil")
|
||||||
local SpearheadEvents = require("classes.spearhead_events")
|
local SpearheadEvents = require("classes.spearhead_events")
|
||||||
local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper")
|
local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper")
|
||||||
local MaxLoadConfig = require("classes.stageClasses.helpers.MaxLoadConfig")
|
local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig")
|
||||||
|
|
||||||
---@class SupplyUnitEventListener
|
---@class SupplyUnitEventListener
|
||||||
---@field supplyUnitSpawned fun(self:SupplyUnitEventListener, unit:Unit) | nil
|
---@field supplyUnitSpawned fun(self:SupplyUnitEventListener, unit:Unit) | nil
|
||||||
@@ -94,7 +94,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---@param listener SupplyUnitEventListener
|
---@param listener SupplyUnitEventListener
|
||||||
function SupplyUnitsTracker:AddOnSupplyUnitSpawnedListener(listener)
|
function SupplyUnitsTracker:AddOnSupplyUnitEventListener(listener)
|
||||||
if listener == nil then return end
|
if listener == nil then return end
|
||||||
|
|
||||||
if self._supplyUnitEventsListeners == nil then
|
if self._supplyUnitEventsListeners == nil then
|
||||||
@@ -104,6 +104,26 @@ function SupplyUnitsTracker:AddOnSupplyUnitSpawnedListener(listener)
|
|||||||
table.insert(self._supplyUnitEventsListeners, listener)
|
table.insert(self._supplyUnitEventsListeners, listener)
|
||||||
end
|
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()
|
function SupplyUnitsTracker:Update()
|
||||||
local players = DcsUtil.getAllPlayerUnits()
|
local players = DcsUtil.getAllPlayerUnits()
|
||||||
for _, player in pairs(players) do
|
for _, player in pairs(players) do
|
||||||
@@ -211,7 +231,6 @@ function SupplyUnitsTracker:CheckUnitsInZones()
|
|||||||
if Util.is3dPointInZone(pos, zone) then
|
if Util.is3dPointInZone(pos, zone) then
|
||||||
if self._unitInSupplyHub[tostring(unit:getID())] ~= true then
|
if self._unitInSupplyHub[tostring(unit:getID())] ~= true then
|
||||||
self._unitInSupplyHub[tostring(unit:getID())] = true
|
self._unitInSupplyHub[tostring(unit:getID())] = true
|
||||||
|
|
||||||
for _, listener in pairs(self._supplyUnitEventsListeners) do
|
for _, listener in pairs(self._supplyUnitEventsListeners) do
|
||||||
pcall(function()
|
pcall(function()
|
||||||
if listener.enteredSupplyHub then
|
if listener.enteredSupplyHub then
|
||||||
@@ -284,9 +303,6 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self:RemoveCargoFromUnit(unitID, crateType)
|
|
||||||
self:UpdateWeightForUnit(unit)
|
|
||||||
|
|
||||||
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
|
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
|
||||||
|
|
||||||
if cargoConfig == nil then
|
if cargoConfig == nil then
|
||||||
@@ -294,7 +310,16 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
|
|||||||
return
|
return
|
||||||
end
|
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
|
cargoCount = cargoCount + 1
|
||||||
local cargoSpawnObject = {
|
local cargoSpawnObject = {
|
||||||
@@ -394,7 +419,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local unitConfig = MaxLoadConfig[unit:getTypeName()]
|
local unitConfig = SupplyLoadConfig[unit:getTypeName()]
|
||||||
if unitConfig == nil then
|
if unitConfig == nil then
|
||||||
trigger.action.outTextForUnit(unit:getID(), "Your unit type is not configured for logistics: " .. crateType, 5)
|
trigger.action.outTextForUnit(unit:getID(), "Your unit type is not configured for logistics: " .. crateType, 5)
|
||||||
self._logger:error("Invalid unit type: " .. unit:getTypeName())
|
self._logger:error("Invalid unit type: " .. unit:getTypeName())
|
||||||
@@ -437,53 +462,173 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class SupplyUnitsBoundingBox
|
||||||
|
---@field min Vec3 World space minimum
|
||||||
|
---@field max Vec3 World space maximum
|
||||||
|
|
||||||
|
---@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
|
||||||
|
|
||||||
|
-- Convert relative bbox to world space by adding object position
|
||||||
|
---@type SupplyUnitsBoundingBox
|
||||||
|
return {
|
||||||
|
min = {
|
||||||
|
x = objPos.x + box.min.x,
|
||||||
|
y = objPos.y + box.min.y,
|
||||||
|
z = objPos.z + box.min.z
|
||||||
|
},
|
||||||
|
max = {
|
||||||
|
x = objPos.x + box.max.x,
|
||||||
|
y = objPos.y + box.max.y,
|
||||||
|
z = objPos.z + box.max.z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
---@private
|
||||||
---@param unit Unit
|
---@param unit Unit
|
||||||
---@return Vec3
|
---@param crateTypeName string
|
||||||
function SupplyUnitsTracker:GetCargoPlacePosition(unit)
|
---@return Vec3?
|
||||||
|
function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
|
||||||
|
|
||||||
local pos = unit:getPosition()
|
local unitPos = unit:getPosition()
|
||||||
local preferredPos = {
|
|
||||||
x = pos.p.x - 10 * pos.x.x,
|
-- Get crate bbox - relative to placement position
|
||||||
y = pos.p.y - 10 * pos.x.y,
|
local crateDesc = StaticObject.getDescByName(crateTypeName) --[[@as table]]
|
||||||
z = pos.p.z - 10 * pos.x.z
|
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
return preferredPos
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
-- local volume = {
|
local occupiedObjects = {}
|
||||||
-- id = world.VolumeType.SPHERE,
|
local found = function(foundItem, val)
|
||||||
-- params = {
|
local bbox = self:GetBoundingBoxes(foundItem)
|
||||||
-- point = preferredPos,
|
if bbox then
|
||||||
-- radius = 10
|
table.insert(occupiedObjects, {
|
||||||
-- }
|
pos = foundItem:getPoint(),
|
||||||
-- }
|
bbox = bbox
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- local occupiedPosX = {}
|
world.searchObjects(searchVolume.id, searchVolume, found)
|
||||||
-- local occupiedPosZ = {}
|
|
||||||
|
|
||||||
-- ---@param foundItem Object
|
local safetyMargin = 3 -- Safety margin around objects
|
||||||
-- local found = function(foundItem, val)
|
|
||||||
|
|
||||||
-- local foundPos = foundItem:getPoint()
|
-- 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 z = math.floor(foundPos.z)
|
-- Search outward in rings starting from minRadius
|
||||||
-- for i = z - 3 , z + 3 do
|
for distance = zone.minRadius, zone.maxRadius, zone.spacing do
|
||||||
-- occupiedPosZ[i] = true
|
-- Check multiple positions within the angular slice
|
||||||
-- end
|
local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections
|
||||||
|
|
||||||
-- local x = math.floor(foundPos.x)
|
for angle = minAngle, maxAngle, angleStep do
|
||||||
-- for i = x - 3 , x + 3 do
|
local radians = math.rad(angle)
|
||||||
-- occupiedPosX[i] = true
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- world.searchObjects(volume.id, volume.params, found)
|
-- Calculate position at this angle and distance
|
||||||
|
-- Angle 0 = forward, 90 = right, 180 = rear, 270 = left
|
||||||
|
local candidateX = unitPos.p.x + distance * math.sin(radians)
|
||||||
|
local candidateY = unitPos.p.y + distance * math.cos(radians)
|
||||||
|
local candidateZ = land.getHeight({ x = candidateX, y = candidateY })
|
||||||
|
|
||||||
|
-- Convert crate's relative bbox to world space at this position
|
||||||
|
local crateBBoxWorldSpace = {
|
||||||
|
min = {
|
||||||
|
x = candidateX + crateRelativeBBox.min.x,
|
||||||
|
y = candidateZ + crateRelativeBBox.min.y,
|
||||||
|
z = candidateY + crateRelativeBBox.min.z
|
||||||
|
},
|
||||||
|
max = {
|
||||||
|
x = candidateX + crateRelativeBBox.max.x,
|
||||||
|
y = candidateZ + crateRelativeBBox.max.y,
|
||||||
|
z = candidateY + crateRelativeBBox.max.z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not collides then
|
||||||
|
return { x = candidateX, y = candidateZ, z = candidateY }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No free spot found
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return SupplyUnitsTracker
|
return SupplyUnitsTracker
|
||||||
@@ -197,7 +197,7 @@ function BuildableMission:SpawnActive()
|
|||||||
self._state = "ACTIVE"
|
self._state = "ACTIVE"
|
||||||
|
|
||||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||||
self._supplyUnitsTracker:AddOnSupplyUnitSpawnedListener(self)
|
self._supplyUnitsTracker:AddOnSupplyUnitEventListener(self)
|
||||||
|
|
||||||
local units = self._supplyUnitsTracker:GetUnits()
|
local units = self._supplyUnitsTracker:GetUnits()
|
||||||
if units then
|
if units then
|
||||||
|
|||||||
Reference in New Issue
Block a user