mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Fixed OnBirthMessage issue and several bugs after live testing.
This commit is contained in:
parent
c7478f75f0
commit
197b49c343
Binary file not shown.
Binary file not shown.
@ -70,18 +70,16 @@ local function addWelcomeMenuForPlayer(playerUnit, playerName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
onPlayerJoin = {}
|
onPlayerJoin = {}
|
||||||
function onPlayerJoin:onEvent(event)
|
function onPlayerJoin:onEvent(event)
|
||||||
env.info("OnBirthMessage: Event triggered - ID: " .. tostring(event.id))
|
-- Only log events we actually process to avoid log spam
|
||||||
|
|
||||||
-- Trigger on both BIRTH and ENGINE_STARTUP events for better coverage
|
|
||||||
if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then
|
if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then
|
||||||
env.info("OnBirthMessage: Correct event type detected")
|
env.info("OnBirthMessage: Event triggered - ID: " .. tostring(event.id))
|
||||||
|
|
||||||
if event.initiator then
|
local initiator = event.initiator
|
||||||
env.info("OnBirthMessage: Initiator exists")
|
if initiator and initiator.getPlayerName then
|
||||||
local playerName = event.initiator:getPlayerName()
|
local playerName = initiator:getPlayerName()
|
||||||
if playerName then
|
if playerName and playerName ~= '' then
|
||||||
env.info("OnBirthMessage: Player name found: " .. playerName)
|
env.info("OnBirthMessage: Player name found: " .. playerName)
|
||||||
|
|
||||||
-- Check if we've already processed this player to prevent doubles (within TTL)
|
-- Check if we've already processed this player to prevent doubles (within TTL)
|
||||||
@ -95,18 +93,21 @@ function onPlayerJoin:onEvent(event)
|
|||||||
|
|
||||||
-- Add error handling to prevent script crashes
|
-- Add error handling to prevent script crashes
|
||||||
local success, errorMsg = pcall(function()
|
local success, errorMsg = pcall(function()
|
||||||
local playerGroup = event.initiator:getGroup()
|
local playerGroup = initiator.getGroup and initiator:getGroup()
|
||||||
local playerUnit = playerGroup:getUnit(1)
|
if not playerGroup then return end
|
||||||
|
local playerUnit = initiator -- the actual player unit that generated the event
|
||||||
local playerSide = playerGroup:getCoalition()
|
local playerSide = playerGroup:getCoalition()
|
||||||
local playerID = playerGroup:getID()
|
local playerID = playerGroup:getID()
|
||||||
local playerAircraft = playerUnit:getTypeName()
|
local playerAircraft = playerUnit and playerUnit.getTypeName and playerUnit:getTypeName() or 'Unknown'
|
||||||
local playerUnitID = playerUnit:getID()
|
local playerUnitID = playerUnit and playerUnit.getID and playerUnit:getID() or nil
|
||||||
|
|
||||||
-- Debug message to confirm script is running
|
-- Debug message to confirm script is running
|
||||||
env.info("OnBirthMessage: Player " .. playerName .. " joined in " .. playerAircraft .. " (Coalition: " .. playerSide .. ")")
|
env.info("OnBirthMessage: Player " .. playerName .. " joined in " .. tostring(playerAircraft) .. " (Coalition: " .. tostring(playerSide) .. ")")
|
||||||
|
|
||||||
-- Send immediate test message
|
-- Send immediate test message
|
||||||
trigger.action.outTextForUnit(playerUnitID, "OnBirthMessage: Script detected you joining as " .. playerName, 15)
|
if playerUnitID then
|
||||||
|
trigger.action.outTextForUnit(playerUnitID, "OnBirthMessage: Script detected you joining as " .. playerName, 15)
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize player preference if not set (default to enabled)
|
-- Initialize player preference if not set (default to enabled)
|
||||||
if playerWelcomeSettings[playerName] == nil then
|
if playerWelcomeSettings[playerName] == nil then
|
||||||
@ -196,14 +197,11 @@ function onPlayerJoin:onEvent(event)
|
|||||||
env.info("OnBirthMessage Error: " .. tostring(errorMsg))
|
env.info("OnBirthMessage Error: " .. tostring(errorMsg))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
env.info("OnBirthMessage: No player name found")
|
-- No player name (AI or non-player object); ignore
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
env.info("OnBirthMessage: No initiator found")
|
-- No initiator or not a Unit; ignore
|
||||||
end
|
end
|
||||||
else
|
|
||||||
-- Uncomment next line if you want to see all events (very spammy)
|
|
||||||
-- env.info("OnBirthMessage: Ignoring event ID: " .. tostring(event.id))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
139077
DCS_Kola/Operation_Polar_Shield/dcs.log
Normal file
139077
DCS_Kola/Operation_Polar_Shield/dcs.log
Normal file
File diff suppressed because it is too large
Load Diff
@ -88,20 +88,20 @@ CTLD.HoverCoachConfig = {
|
|||||||
thresholds = {
|
thresholds = {
|
||||||
arrivalDist = 1000, -- m: start guidance “You’re close…”
|
arrivalDist = 1000, -- m: start guidance “You’re close…”
|
||||||
closeDist = 100, -- m: reduce speed / set AGL guidance
|
closeDist = 100, -- m: reduce speed / set AGL guidance
|
||||||
precisionDist = 30, -- m: start precision hints
|
precisionDist = 10, -- m: start precision hints
|
||||||
captureHoriz = 2, -- m: horizontal sweet spot radius
|
captureHoriz = 4, -- m: horizontal sweet spot radius
|
||||||
captureVert = 2, -- m: vertical sweet spot tolerance around AGL window
|
captureVert = 4, -- m: vertical sweet spot tolerance around AGL window
|
||||||
aglMin = 5, -- m: hover window min AGL
|
aglMin = 5, -- m: hover window min AGL
|
||||||
aglMax = 20, -- m: hover window max AGL
|
aglMax = 20, -- m: hover window max AGL
|
||||||
maxGS = 8/3.6, -- m/s: 8 km/h for precision, used for errors
|
maxGS = 8/3.6, -- m/s: 8 km/h for precision, used for errors
|
||||||
captureGS = 4/3.6, -- m/s: 4 km/h capture requirement
|
captureGS = 4/3.6, -- m/s: 4 km/h capture requirement
|
||||||
maxVS = 1.5, -- m/s: absolute vertical speed during capture
|
maxVS = 1.5, -- m/s: absolute vertical speed during capture
|
||||||
driftResetDist = 35, -- m: if beyond, reset precision phase
|
driftResetDist = 20, -- m: if beyond, reset precision phase
|
||||||
stabilityHold = 1.8 -- s: hold steady before loading
|
stabilityHold = 1.8 -- s: hold steady before loading
|
||||||
},
|
},
|
||||||
|
|
||||||
throttle = {
|
throttle = {
|
||||||
coachUpdate = 1.5, -- s between hint updates in precision
|
coachUpdate = 2, -- s between hint updates in precision
|
||||||
generic = 3.0, -- s between non-coach messages
|
generic = 3.0, -- s between non-coach messages
|
||||||
repeatSame = 6.0 -- s before repeating same message key
|
repeatSame = 6.0 -- s before repeating same message key
|
||||||
},
|
},
|
||||||
@ -186,8 +186,9 @@ CTLD.Config = {
|
|||||||
UseCategorySubmenus = true, -- if true, organize crate requests by category submenu (menuCategory)
|
UseCategorySubmenus = true, -- if true, organize crate requests by category submenu (menuCategory)
|
||||||
UseBuiltinCatalog = false, -- if false, starts with an empty catalog; intended when you preload a global catalog and want only that
|
UseBuiltinCatalog = false, -- if false, starts with an empty catalog; intended when you preload a global catalog and want only that
|
||||||
-- Safety offsets to avoid spawning units too close to player aircraft
|
-- Safety offsets to avoid spawning units too close to player aircraft
|
||||||
BuildSpawnOffset = 25, -- meters: shift build point forward from the aircraft to avoid rotor/ground collisions (0 = spawn centered on aircraft)
|
BuildSpawnOffset = 40, -- meters: shift build point forward from the aircraft to avoid rotor/ground collisions (0 = spawn centered on aircraft)
|
||||||
TroopSpawnOffset = 25, -- meters: shift troop unload point forward from the aircraft
|
TroopSpawnOffset = 40, -- meters: shift troop unload point forward from the aircraft
|
||||||
|
DropCrateForwardOffset = 20, -- meters: drop loaded crates this far in front of the aircraft (instead of directly under)
|
||||||
RestrictFOBToZones = false, -- if true, recipes marked isFOB only build inside configured FOBZones
|
RestrictFOBToZones = false, -- if true, recipes marked isFOB only build inside configured FOBZones
|
||||||
AutoBuildFOBInZones = false, -- if true, CTLD auto-builds FOB recipes when required crates are inside a FOB zone
|
AutoBuildFOBInZones = false, -- if true, CTLD auto-builds FOB recipes when required crates are inside a FOB zone
|
||||||
BuildRadius = 60, -- meters around build point to collect crates
|
BuildRadius = 60, -- meters around build point to collect crates
|
||||||
@ -195,8 +196,8 @@ CTLD.Config = {
|
|||||||
MessageDuration = 15, -- seconds for on-screen messages
|
MessageDuration = 15, -- seconds for on-screen messages
|
||||||
Debug = false,
|
Debug = false,
|
||||||
-- Build safety
|
-- Build safety
|
||||||
BuildConfirmEnabled = true, -- require a second confirmation within a short window before building
|
BuildConfirmEnabled = false, -- require a second confirmation within a short window before building
|
||||||
BuildConfirmWindowSeconds = 10, -- seconds allowed between first and second "Build Here" press
|
BuildConfirmWindowSeconds = 30, -- seconds allowed between first and second "Build Here" press
|
||||||
BuildCooldownEnabled = true, -- after a successful build, impose a cooldown before allowing another build by the same group
|
BuildCooldownEnabled = true, -- after a successful build, impose a cooldown before allowing another build by the same group
|
||||||
BuildCooldownSeconds = 60, -- seconds of cooldown after a successful build per group
|
BuildCooldownSeconds = 60, -- seconds of cooldown after a successful build per group
|
||||||
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
|
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
|
||||||
@ -286,6 +287,52 @@ CTLD.Config = {
|
|||||||
MaxSpeedMPS = 5 -- max allowed speed in m/s for hover pickup
|
MaxSpeedMPS = 5 -- max allowed speed in m/s for hover pickup
|
||||||
},
|
},
|
||||||
|
|
||||||
|
-- Troop type presets (menu-driven loadable teams)
|
||||||
|
Troops = {
|
||||||
|
-- Default troop type to use when no specific type is chosen
|
||||||
|
DefaultType = 'AS',
|
||||||
|
-- Team definitions: label (menu text), size (number spawned), and unit pools per coalition
|
||||||
|
-- NOTE: Unit type names are DCS database strings. The provided defaults are conservative and
|
||||||
|
-- use generic infantry to maximize compatibility. You can customize per mission/era.
|
||||||
|
TroopTypes = {
|
||||||
|
-- Assault squad: general-purpose rifles/MG
|
||||||
|
AS = {
|
||||||
|
label = 'Assault Squad',
|
||||||
|
size = 8,
|
||||||
|
-- Fallback pools; adjust for era/faction if you want richer mixes
|
||||||
|
unitsBlue = { 'Infantry M4', 'Infantry M249' },
|
||||||
|
unitsRed = { 'Infantry AK', 'Infantry AK' },
|
||||||
|
-- If specific Blue/Red not available, this generic pool is used
|
||||||
|
units = { 'Infantry AK' },
|
||||||
|
},
|
||||||
|
-- Anti-air team: MANPADS element
|
||||||
|
AA = {
|
||||||
|
label = 'MANPADS Team',
|
||||||
|
size = 4,
|
||||||
|
-- These names vary by mod/DB; defaults fall back to generic infantry if unavailable
|
||||||
|
unitsBlue = { 'Infantry manpad Stinger', 'Infantry M4' },
|
||||||
|
unitsRed = { 'Infantry manpad Igla', 'Infantry AK' },
|
||||||
|
units = { 'Infantry AK' },
|
||||||
|
},
|
||||||
|
-- Anti-tank team: RPG/AT4 element
|
||||||
|
AT = {
|
||||||
|
label = 'AT Team',
|
||||||
|
size = 4,
|
||||||
|
unitsBlue = { 'Soldier M136', 'Infantry M4' },
|
||||||
|
unitsRed = { 'Soldier RPG', 'Infantry AK' },
|
||||||
|
units = { 'Infantry AK' },
|
||||||
|
},
|
||||||
|
-- Indirect fire: mortar detachment
|
||||||
|
AR = {
|
||||||
|
label = 'Mortar Team',
|
||||||
|
size = 2,
|
||||||
|
unitsBlue = { 'Mortar M252' },
|
||||||
|
unitsRed = { '2B11 mortar' },
|
||||||
|
units = { 'Infantry AK' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
-- #endregion Config
|
-- #endregion Config
|
||||||
@ -819,6 +866,23 @@ local function _bearingDeg(from, to)
|
|||||||
return math.floor(ang + 0.5)
|
return math.floor(ang + 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Normalize MOOSE/DCS heading to both radians and degrees consistently.
|
||||||
|
-- Some environments may yield degrees; others radians. This returns (rad, deg).
|
||||||
|
local function _headingRadDeg(unit)
|
||||||
|
local h = (unit and unit.GetHeading and unit:GetHeading()) or 0
|
||||||
|
local hrad, hdeg
|
||||||
|
if h and h > (2*math.pi + 0.1) then
|
||||||
|
-- Looks like degrees
|
||||||
|
hdeg = h % 360
|
||||||
|
hrad = math.rad(hdeg)
|
||||||
|
else
|
||||||
|
-- Radians (normalize into [0, 2pi))
|
||||||
|
hrad = (h or 0) % (2*math.pi)
|
||||||
|
hdeg = math.deg(hrad)
|
||||||
|
end
|
||||||
|
return hrad, hdeg
|
||||||
|
end
|
||||||
|
|
||||||
local function _projectToBodyFrame(dx, dz, hdg)
|
local function _projectToBodyFrame(dx, dz, hdg)
|
||||||
-- world (east=X=dx, north=Z=dz) to body frame (fwd/right)
|
-- world (east=X=dx, north=Z=dz) to body frame (fwd/right)
|
||||||
local fwd = dx * math.sin(hdg) + dz * math.cos(hdg)
|
local fwd = dx * math.sin(hdg) + dz * math.cos(hdg)
|
||||||
@ -1444,6 +1508,27 @@ function CTLD:BuildGroupMenus(group)
|
|||||||
-- Operations -> Troop Transport
|
-- Operations -> Troop Transport
|
||||||
local troopsRoot = MENU_GROUP:New(group, 'Troop Transport', opsRoot)
|
local troopsRoot = MENU_GROUP:New(group, 'Troop Transport', opsRoot)
|
||||||
CMD('Load Troops', troopsRoot, function() self:LoadTroops(group) end)
|
CMD('Load Troops', troopsRoot, function() self:LoadTroops(group) end)
|
||||||
|
-- Optional typed troop loading submenu
|
||||||
|
do
|
||||||
|
local typedRoot = MENU_GROUP:New(group, 'Load Troops (Type)', troopsRoot)
|
||||||
|
local tcfg = (self.Config.Troops and self.Config.Troops.TroopTypes) or {}
|
||||||
|
-- Stable order per common roles
|
||||||
|
local order = { 'AS', 'AA', 'AT', 'AR' }
|
||||||
|
local seen = {}
|
||||||
|
local function addItem(key)
|
||||||
|
local def = tcfg[key]
|
||||||
|
if not def then return end
|
||||||
|
local label = (def.label or key)
|
||||||
|
local size = def.size or 6
|
||||||
|
CMD(string.format('%s (%d)', label, size), typedRoot, function()
|
||||||
|
self:LoadTroops(group, { typeKey = key })
|
||||||
|
end)
|
||||||
|
seen[key] = true
|
||||||
|
end
|
||||||
|
for _,k in ipairs(order) do addItem(k) end
|
||||||
|
-- Add any additional custom types not in the default order
|
||||||
|
for k,_ in pairs(tcfg) do if not seen[k] then addItem(k) end end
|
||||||
|
end
|
||||||
do
|
do
|
||||||
local tr = (self.Config.AttackAI and self.Config.AttackAI.TroopSearchRadius) or 3000
|
local tr = (self.Config.AttackAI and self.Config.AttackAI.TroopSearchRadius) or 3000
|
||||||
CMD('Deploy [Hold Position]', troopsRoot, function() self:UnloadTroops(group, { behavior = 'defend' }) end)
|
CMD('Deploy [Hold Position]', troopsRoot, function() self:UnloadTroops(group, { behavior = 'defend' }) end)
|
||||||
@ -1538,7 +1623,14 @@ function CTLD:BuildGroupMenus(group)
|
|||||||
end
|
end
|
||||||
if bestName and bestMeta then
|
if bestName and bestMeta then
|
||||||
local zdef = { smoke = self.Config.PickupZoneSmokeColor }
|
local zdef = { smoke = self.Config.PickupZoneSmokeColor }
|
||||||
trigger.action.smoke({ x = bestMeta.point.x, z = bestMeta.point.z }, (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor)
|
local sx, sz = bestMeta.point.x, bestMeta.point.z
|
||||||
|
local sy = 0
|
||||||
|
if land and land.getHeight then
|
||||||
|
-- land.getHeight expects Vec2 where y is z
|
||||||
|
local ok, h = pcall(land.getHeight, { x = sx, y = sz })
|
||||||
|
if ok and type(h) == 'number' then sy = h end
|
||||||
|
end
|
||||||
|
trigger.action.smoke({ x = sx, y = sy, z = sz }, (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor)
|
||||||
_eventSend(self, group, nil, 'crate_re_marked', { id = bestName, mark = 'smoke' })
|
_eventSend(self, group, nil, 'crate_re_marked', { id = bestName, mark = 'smoke' })
|
||||||
else
|
else
|
||||||
_msgGroup(group, 'No friendly crates found to mark.')
|
_msgGroup(group, 'No friendly crates found to mark.')
|
||||||
@ -1552,7 +1644,8 @@ function CTLD:BuildGroupMenus(group)
|
|||||||
local unit = group:GetUnit(1)
|
local unit = group:GetUnit(1)
|
||||||
if not unit or not unit:IsAlive() then return end
|
if not unit or not unit:IsAlive() then return end
|
||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
trigger.action.smoke({ x = p.x, z = p.z }, color)
|
-- Use full Vec3 to ensure correct placement
|
||||||
|
trigger.action.smoke({ x = p.x, y = p.y, z = p.z }, color)
|
||||||
end
|
end
|
||||||
MENU_GROUP_COMMAND:New(group, 'Green', smokeRoot, function() smokeHere(trigger.smokeColor.Green) end)
|
MENU_GROUP_COMMAND:New(group, 'Green', smokeRoot, function() smokeHere(trigger.smokeColor.Green) end)
|
||||||
MENU_GROUP_COMMAND:New(group, 'Red', smokeRoot, function() smokeHere(trigger.smokeColor.Red) end)
|
MENU_GROUP_COMMAND:New(group, 'Red', smokeRoot, function() smokeHere(trigger.smokeColor.Red) end)
|
||||||
@ -1705,9 +1798,9 @@ function CTLD:BuildGroupMenus(group)
|
|||||||
if self._getZoneCenterAndRadius then center = select(1, self:_getZoneCenterAndRadius(bestZone)) end
|
if self._getZoneCenterAndRadius then center = select(1, self:_getZoneCenterAndRadius(bestZone)) end
|
||||||
if not center then
|
if not center then
|
||||||
local v3 = bestZone:GetPointVec3()
|
local v3 = bestZone:GetPointVec3()
|
||||||
center = { x = v3.x, z = v3.z }
|
center = { x = v3.x, y = v3.y or 0, z = v3.z }
|
||||||
else
|
else
|
||||||
center = { x = center.x, z = center.z }
|
center = { x = center.x, y = center.y or 0, z = center.z }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Choose smoke color per kind (fallbacks if not configured)
|
-- Choose smoke color per kind (fallbacks if not configured)
|
||||||
@ -1716,7 +1809,7 @@ function CTLD:BuildGroupMenus(group)
|
|||||||
or trigger.smokeColor.White
|
or trigger.smokeColor.White
|
||||||
|
|
||||||
if trigger and trigger.action and trigger.action.smoke then
|
if trigger and trigger.action and trigger.action.smoke then
|
||||||
trigger.action.smoke({ x = center.x, z = center.z }, color)
|
trigger.action.smoke(center, color)
|
||||||
_msgGroup(group, string.format('Smoked nearest %s zone: %s', bestKind, bestZone:GetName()))
|
_msgGroup(group, string.format('Smoked nearest %s zone: %s', bestKind, bestZone:GetName()))
|
||||||
else
|
else
|
||||||
_msgGroup(group, 'Smoke not available in this environment.')
|
_msgGroup(group, 'Smoke not available in this environment.')
|
||||||
@ -1885,9 +1978,9 @@ function CTLD:_BuildOrRefreshBuildAdvancedMenu(group, rootMenu)
|
|||||||
if not unit or not unit:IsAlive() then return end
|
if not unit or not unit:IsAlive() then return end
|
||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
local here = { x = p.x, z = p.z }
|
local here = { x = p.x, z = p.z }
|
||||||
local hdg = unit:GetHeading() or 0
|
local hdgRad, _ = _headingRadDeg(unit)
|
||||||
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
||||||
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdg) * buildOffset, z = here.z + math.cos(hdg) * buildOffset } or { x = here.x, z = here.z }
|
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdgRad) * buildOffset, z = here.z + math.cos(hdgRad) * buildOffset } or { x = here.x, z = here.z }
|
||||||
local radius = self.Config.BuildRadius or 60
|
local radius = self.Config.BuildRadius or 60
|
||||||
local nearby = self:GetNearbyCrates(here, radius)
|
local nearby = self:GetNearbyCrates(here, radius)
|
||||||
local filtered = {}
|
local filtered = {}
|
||||||
@ -2001,9 +2094,9 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
|||||||
|
|
||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
local here = { x = p.x, z = p.z }
|
local here = { x = p.x, z = p.z }
|
||||||
local hdg = unit:GetHeading() or 0
|
local hdgRad, hdgDeg = _headingRadDeg(unit)
|
||||||
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
||||||
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdg) * buildOffset, z = here.z + math.cos(hdg) * buildOffset } or { x = here.x, z = here.z }
|
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdgRad) * buildOffset, z = here.z + math.cos(hdgRad) * buildOffset } or { x = here.x, z = here.z }
|
||||||
local radius = self.Config.BuildRadius or 60
|
local radius = self.Config.BuildRadius or 60
|
||||||
local nearby = self:GetNearbyCrates(here, radius)
|
local nearby = self:GetNearbyCrates(here, radius)
|
||||||
local filtered = {}
|
local filtered = {}
|
||||||
@ -2197,7 +2290,7 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
|||||||
-- Verify counts and build
|
-- Verify counts and build
|
||||||
if type(def.requires) == 'table' then
|
if type(def.requires) == 'table' then
|
||||||
for reqKey,qty in pairs(def.requires) do if (counts[reqKey] or 0) < (qty or 0) then _eventSend(self, group, nil, 'build_insufficient_crates', { build = def.description or recipeKey }); return end end
|
for reqKey,qty in pairs(def.requires) do if (counts[reqKey] or 0) < (qty or 0) then _eventSend(self, group, nil, 'build_insufficient_crates', { build = def.description or recipeKey }); return end end
|
||||||
local gdata = def.build({ x = spawnAt.x, z = spawnAt.z }, math.deg(hdg), def.side or self.Side)
|
local gdata = def.build({ x = spawnAt.x, z = spawnAt.z }, hdgDeg, def.side or self.Side)
|
||||||
_eventSend(self, group, nil, 'build_started', { build = def.description or recipeKey })
|
_eventSend(self, group, nil, 'build_started', { build = def.description or recipeKey })
|
||||||
local g = _coalitionAddGroup(def.side or self.Side, def.category or Group.Category.GROUND, gdata)
|
local g = _coalitionAddGroup(def.side or self.Side, def.category or Group.Category.GROUND, gdata)
|
||||||
if not g then _eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' }); return end
|
if not g then _eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' }); return end
|
||||||
@ -2230,7 +2323,7 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
|||||||
-- single-key
|
-- single-key
|
||||||
local need = def.required or 1
|
local need = def.required or 1
|
||||||
if (counts[recipeKey] or 0) < need then _eventSend(self, group, nil, 'build_insufficient_crates', { build = def.description or recipeKey }); return end
|
if (counts[recipeKey] or 0) < need then _eventSend(self, group, nil, 'build_insufficient_crates', { build = def.description or recipeKey }); return end
|
||||||
local gdata = def.build({ x = spawnAt.x, z = spawnAt.z }, math.deg(hdg), def.side or self.Side)
|
local gdata = def.build({ x = spawnAt.x, z = spawnAt.z }, hdgDeg, def.side or self.Side)
|
||||||
_eventSend(self, group, nil, 'build_started', { build = def.description or recipeKey })
|
_eventSend(self, group, nil, 'build_started', { build = def.description or recipeKey })
|
||||||
local g = _coalitionAddGroup(def.side or self.Side, def.category or Group.Category.GROUND, gdata)
|
local g = _coalitionAddGroup(def.side or self.Side, def.category or Group.Category.GROUND, gdata)
|
||||||
if not g then _eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' }); return end
|
if not g then _eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' }); return end
|
||||||
@ -2638,7 +2731,13 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
|||||||
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
|
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
|
||||||
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
|
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
|
||||||
if smokeColor then
|
if smokeColor then
|
||||||
trigger.action.smoke({ x = spawnPoint.x, z = spawnPoint.z }, smokeColor)
|
local sx, sz = spawnPoint.x, spawnPoint.z
|
||||||
|
local sy = 0
|
||||||
|
if land and land.getHeight then
|
||||||
|
local ok, h = pcall(land.getHeight, { x = sx, y = sz })
|
||||||
|
if ok and type(h) == 'number' then sy = h end
|
||||||
|
end
|
||||||
|
trigger.action.smoke({ x = sx, y = sy, z = sz }, smokeColor)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Either require a pickup zone proximity, or fallback to near-aircraft spawn (legacy behavior)
|
-- Either require a pickup zone proximity, or fallback to near-aircraft spawn (legacy behavior)
|
||||||
@ -2821,9 +2920,9 @@ function CTLD:BuildAtGroup(group, opts)
|
|||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
local here = { x = p.x, z = p.z }
|
local here = { x = p.x, z = p.z }
|
||||||
-- Compute a safe spawn point offset forward from the aircraft to prevent rotor/ground collisions with spawned units
|
-- Compute a safe spawn point offset forward from the aircraft to prevent rotor/ground collisions with spawned units
|
||||||
local hdg = unit:GetHeading() or 0
|
local hdgRad, hdgDeg = _headingRadDeg(unit)
|
||||||
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
local buildOffset = math.max(0, tonumber(self.Config.BuildSpawnOffset or 0) or 0)
|
||||||
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdg) * buildOffset, z = here.z + math.cos(hdg) * buildOffset } or { x = here.x, z = here.z }
|
local spawnAt = (buildOffset > 0) and { x = here.x + math.sin(hdgRad) * buildOffset, z = here.z + math.cos(hdgRad) * buildOffset } or { x = here.x, z = here.z }
|
||||||
local radius = self.Config.BuildRadius
|
local radius = self.Config.BuildRadius
|
||||||
local nearby = self:GetNearbyCrates(here, radius)
|
local nearby = self:GetNearbyCrates(here, radius)
|
||||||
-- filter crates to coalition side for this CTLD instance
|
-- filter crates to coalition side for this CTLD instance
|
||||||
@ -2893,7 +2992,7 @@ function CTLD:BuildAtGroup(group, opts)
|
|||||||
if (counts[reqKey] or 0) < qty then ok = false; break end
|
if (counts[reqKey] or 0) < qty then ok = false; break end
|
||||||
end
|
end
|
||||||
if ok then
|
if ok then
|
||||||
local gdata = cat.build({ x = spawnAt.x, z = spawnAt.z }, math.deg(hdg), cat.side or self.Side)
|
local gdata = cat.build({ x = spawnAt.x, z = spawnAt.z }, hdgDeg, cat.side or self.Side)
|
||||||
_eventSend(self, group, nil, 'build_started', { build = cat.description or recipeKey })
|
_eventSend(self, group, nil, 'build_started', { build = cat.description or recipeKey })
|
||||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||||
if g then
|
if g then
|
||||||
@ -2944,7 +3043,7 @@ function CTLD:BuildAtGroup(group, opts)
|
|||||||
fobBlocked = true
|
fobBlocked = true
|
||||||
else
|
else
|
||||||
-- Build caps disabled: rely solely on inventory/catalog control
|
-- Build caps disabled: rely solely on inventory/catalog control
|
||||||
local gdata = cat.build({ x = spawnAt.x, z = spawnAt.z }, math.deg(hdg), cat.side or self.Side)
|
local gdata = cat.build({ x = spawnAt.x, z = spawnAt.z }, hdgDeg, cat.side or self.Side)
|
||||||
_eventSend(self, group, nil, 'build_started', { build = cat.description or key })
|
_eventSend(self, group, nil, 'build_started', { build = cat.description or key })
|
||||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||||
if g then
|
if g then
|
||||||
@ -3077,6 +3176,10 @@ function CTLD:DropLoadedCrates(group, howMany)
|
|||||||
end
|
end
|
||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
local here = { x = p.x, z = p.z }
|
local here = { x = p.x, z = p.z }
|
||||||
|
-- Offset drop point forward of the aircraft to avoid rotor/airframe damage
|
||||||
|
local hdgRad, _ = _headingRadDeg(unit)
|
||||||
|
local fwd = math.max(0, tonumber(self.Config.DropCrateForwardOffset or 20) or 0)
|
||||||
|
local dropPt = (fwd > 0) and { x = here.x + math.sin(hdgRad) * fwd, z = here.z + math.cos(hdgRad) * fwd } or { x = here.x, z = here.z }
|
||||||
local initialTotal = lc.total or 0
|
local initialTotal = lc.total or 0
|
||||||
local requested = (howMany and howMany > 0) and howMany or initialTotal
|
local requested = (howMany and howMany > 0) and howMany or initialTotal
|
||||||
local toDrop = math.min(requested, initialTotal)
|
local toDrop = math.min(requested, initialTotal)
|
||||||
@ -3094,8 +3197,8 @@ function CTLD:DropLoadedCrates(group, howMany)
|
|||||||
for i=1,dropNow do
|
for i=1,dropNow do
|
||||||
local cname = string.format('CTLD_CRATE_%s_%d', k, math.random(100000,999999))
|
local cname = string.format('CTLD_CRATE_%s_%d', k, math.random(100000,999999))
|
||||||
local cat = self.Config.CrateCatalog[k]
|
local cat = self.Config.CrateCatalog[k]
|
||||||
_spawnStaticCargo(self.Side, here, (cat and cat.dcsCargoType) or 'uh1h_cargo', cname)
|
_spawnStaticCargo(self.Side, dropPt, (cat and cat.dcsCargoType) or 'uh1h_cargo', cname)
|
||||||
CTLD._crates[cname] = { key = k, side = self.Side, spawnTime = timer.getTime(), point = { x = here.x, z = here.z } }
|
CTLD._crates[cname] = { key = k, side = self.Side, spawnTime = timer.getTime(), point = { x = dropPt.x, z = dropPt.z } }
|
||||||
lc.byKey[k] = lc.byKey[k] - 1
|
lc.byKey[k] = lc.byKey[k] - 1
|
||||||
if lc.byKey[k] <= 0 then lc.byKey[k] = nil end
|
if lc.byKey[k] <= 0 then lc.byKey[k] = nil end
|
||||||
lc.total = lc.total - 1
|
lc.total = lc.total - 1
|
||||||
@ -3182,7 +3285,7 @@ function CTLD:ScanHoverPickup()
|
|||||||
|
|
||||||
-- Precision phase
|
-- Precision phase
|
||||||
if bestd <= (coachCfg.thresholds.precisionDist or 30) then
|
if bestd <= (coachCfg.thresholds.precisionDist or 30) then
|
||||||
local hdg = unit:GetHeading() or 0
|
local hdg, _ = _headingRadDeg(unit)
|
||||||
local dx = (bestMeta.point.x - p3.x)
|
local dx = (bestMeta.point.x - p3.x)
|
||||||
local dz = (bestMeta.point.z - p3.z)
|
local dz = (bestMeta.point.z - p3.z)
|
||||||
local right, fwd = _projectToBodyFrame(dx, dz, hdg)
|
local right, fwd = _projectToBodyFrame(dx, dz, hdg)
|
||||||
@ -3363,12 +3466,16 @@ function CTLD:LoadTroops(group, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local capacity = 6 -- simple default; can be adjusted per type later
|
-- Determine troop type and composition
|
||||||
|
local requestedType = (opts and (opts.typeKey or opts.type))
|
||||||
|
or (self.Config.Troops and self.Config.Troops.DefaultType)
|
||||||
|
or 'AS'
|
||||||
|
local unitsList, label = self:_resolveTroopUnits(requestedType)
|
||||||
CTLD._troopsLoaded[gname] = {
|
CTLD._troopsLoaded[gname] = {
|
||||||
count = capacity,
|
count = #unitsList,
|
||||||
typeKey = 'RIFLE',
|
typeKey = requestedType,
|
||||||
}
|
}
|
||||||
_eventSend(self, group, nil, 'troops_loaded', { count = capacity })
|
_eventSend(self, group, nil, 'troops_loaded', { count = #unitsList })
|
||||||
end
|
end
|
||||||
|
|
||||||
function CTLD:UnloadTroops(group, opts)
|
function CTLD:UnloadTroops(group, opts)
|
||||||
@ -3392,18 +3499,22 @@ function CTLD:UnloadTroops(group, opts)
|
|||||||
end
|
end
|
||||||
local p = unit:GetPointVec3()
|
local p = unit:GetPointVec3()
|
||||||
local here = { x = p.x, z = p.z }
|
local here = { x = p.x, z = p.z }
|
||||||
local hdg = unit:GetHeading() or 0
|
local hdgRad, _ = _headingRadDeg(unit)
|
||||||
-- Offset troop spawn forward to avoid spawning under/near rotors
|
-- Offset troop spawn forward to avoid spawning under/near rotors
|
||||||
local troopOffset = math.max(0, tonumber(self.Config.TroopSpawnOffset or 0) or 0)
|
local troopOffset = math.max(0, tonumber(self.Config.TroopSpawnOffset or 0) or 0)
|
||||||
local center = (troopOffset > 0) and { x = here.x + math.sin(hdg) * troopOffset, z = here.z + math.cos(hdg) * troopOffset } or { x = here.x, z = here.z }
|
local center = (troopOffset > 0) and { x = here.x + math.sin(hdgRad) * troopOffset, z = here.z + math.cos(hdgRad) * troopOffset } or { x = here.x, z = here.z }
|
||||||
|
|
||||||
local count = load.count
|
-- Build the unit composition based on type
|
||||||
-- Spawn a simple infantry fireteam
|
local comp, _ = self:_resolveTroopUnits(load.typeKey)
|
||||||
local units = {}
|
local units = {}
|
||||||
for i=1, math.min(count, 8) do
|
local spacing = 1.8
|
||||||
|
for i=1, #comp do
|
||||||
|
local dx = (i-1) * spacing
|
||||||
|
local dz = ((i % 2) == 0) and 2.0 or -2.0
|
||||||
table.insert(units, {
|
table.insert(units, {
|
||||||
type = 'Infantry AK', name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
type = tostring(comp[i] or 'Infantry AK'),
|
||||||
x = center.x + i*1.5, y = center.z + (i%2==0 and 2 or -2), heading = hdg
|
name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
||||||
|
x = center.x + dx, y = center.z + dz, heading = hdgRad
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
local groupData = {
|
local groupData = {
|
||||||
@ -3439,6 +3550,27 @@ function CTLD:UnloadTroops(group, opts)
|
|||||||
end
|
end
|
||||||
-- #endregion Troops
|
-- #endregion Troops
|
||||||
|
|
||||||
|
-- Internal: resolve troop composition list for a given type key and coalition
|
||||||
|
function CTLD:_resolveTroopUnits(typeKey)
|
||||||
|
local tcfg = (self.Config.Troops and self.Config.Troops.TroopTypes) or {}
|
||||||
|
local def = tcfg[typeKey or 'AS'] or {}
|
||||||
|
local size = tonumber(def.size or 0) or 0
|
||||||
|
if size <= 0 then size = 6 end
|
||||||
|
local pool
|
||||||
|
if self.Side == coalition.side.BLUE then
|
||||||
|
pool = def.unitsBlue or def.units
|
||||||
|
elseif self.Side == coalition.side.RED then
|
||||||
|
pool = def.unitsRed or def.units
|
||||||
|
else
|
||||||
|
pool = def.units
|
||||||
|
end
|
||||||
|
if not pool or #pool == 0 then pool = { 'Infantry AK' } end
|
||||||
|
local list = {}
|
||||||
|
for i=1,size do list[i] = pool[((i-1) % #pool) + 1] end
|
||||||
|
local label = def.label or typeKey or 'Troops'
|
||||||
|
return list, label
|
||||||
|
end
|
||||||
|
|
||||||
-- =========================
|
-- =========================
|
||||||
-- Public helpers
|
-- Public helpers
|
||||||
-- =========================
|
-- =========================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user