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.
@ -72,16 +72,14 @@ end
|
||||
|
||||
onPlayerJoin = {}
|
||||
function onPlayerJoin:onEvent(event)
|
||||
-- Only log events we actually process to avoid log spam
|
||||
if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then
|
||||
env.info("OnBirthMessage: Event triggered - ID: " .. tostring(event.id))
|
||||
|
||||
-- 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
|
||||
env.info("OnBirthMessage: Correct event type detected")
|
||||
|
||||
if event.initiator then
|
||||
env.info("OnBirthMessage: Initiator exists")
|
||||
local playerName = event.initiator:getPlayerName()
|
||||
if playerName then
|
||||
local initiator = event.initiator
|
||||
if initiator and initiator.getPlayerName then
|
||||
local playerName = initiator:getPlayerName()
|
||||
if playerName and playerName ~= '' then
|
||||
env.info("OnBirthMessage: Player name found: " .. playerName)
|
||||
|
||||
-- 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
|
||||
local success, errorMsg = pcall(function()
|
||||
local playerGroup = event.initiator:getGroup()
|
||||
local playerUnit = playerGroup:getUnit(1)
|
||||
local playerGroup = initiator.getGroup and initiator:getGroup()
|
||||
if not playerGroup then return end
|
||||
local playerUnit = initiator -- the actual player unit that generated the event
|
||||
local playerSide = playerGroup:getCoalition()
|
||||
local playerID = playerGroup:getID()
|
||||
local playerAircraft = playerUnit:getTypeName()
|
||||
local playerUnitID = playerUnit:getID()
|
||||
local playerAircraft = playerUnit and playerUnit.getTypeName and playerUnit:getTypeName() or 'Unknown'
|
||||
local playerUnitID = playerUnit and playerUnit.getID and playerUnit:getID() or nil
|
||||
|
||||
-- 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
|
||||
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)
|
||||
if playerWelcomeSettings[playerName] == nil then
|
||||
@ -196,14 +197,11 @@ function onPlayerJoin:onEvent(event)
|
||||
env.info("OnBirthMessage Error: " .. tostring(errorMsg))
|
||||
end
|
||||
else
|
||||
env.info("OnBirthMessage: No player name found")
|
||||
-- No player name (AI or non-player object); ignore
|
||||
end
|
||||
else
|
||||
env.info("OnBirthMessage: No initiator found")
|
||||
-- No initiator or not a Unit; ignore
|
||||
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
|
||||
|
||||
|
||||
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 = {
|
||||
arrivalDist = 1000, -- m: start guidance “You’re close…”
|
||||
closeDist = 100, -- m: reduce speed / set AGL guidance
|
||||
precisionDist = 30, -- m: start precision hints
|
||||
captureHoriz = 2, -- m: horizontal sweet spot radius
|
||||
captureVert = 2, -- m: vertical sweet spot tolerance around AGL window
|
||||
precisionDist = 10, -- m: start precision hints
|
||||
captureHoriz = 4, -- m: horizontal sweet spot radius
|
||||
captureVert = 4, -- m: vertical sweet spot tolerance around AGL window
|
||||
aglMin = 5, -- m: hover window min AGL
|
||||
aglMax = 20, -- m: hover window max AGL
|
||||
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
|
||||
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
|
||||
},
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
BuildSpawnOffset = 25, -- 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
|
||||
BuildSpawnOffset = 40, -- meters: shift build point forward from the aircraft to avoid rotor/ground collisions (0 = spawn centered on 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
|
||||
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
|
||||
@ -195,8 +196,8 @@ CTLD.Config = {
|
||||
MessageDuration = 15, -- seconds for on-screen messages
|
||||
Debug = false,
|
||||
-- Build safety
|
||||
BuildConfirmEnabled = true, -- require a second confirmation within a short window before building
|
||||
BuildConfirmWindowSeconds = 10, -- seconds allowed between first and second "Build Here" press
|
||||
BuildConfirmEnabled = false, -- require a second confirmation within a short window before building
|
||||
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
|
||||
BuildCooldownSeconds = 60, -- seconds of cooldown after a successful build per group
|
||||
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
|
||||
},
|
||||
|
||||
-- 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
|
||||
@ -819,6 +866,23 @@ local function _bearingDeg(from, to)
|
||||
return math.floor(ang + 0.5)
|
||||
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)
|
||||
-- world (east=X=dx, north=Z=dz) to body frame (fwd/right)
|
||||
local fwd = dx * math.sin(hdg) + dz * math.cos(hdg)
|
||||
@ -1444,6 +1508,27 @@ function CTLD:BuildGroupMenus(group)
|
||||
-- Operations -> Troop Transport
|
||||
local troopsRoot = MENU_GROUP:New(group, 'Troop Transport', opsRoot)
|
||||
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
|
||||
local tr = (self.Config.AttackAI and self.Config.AttackAI.TroopSearchRadius) or 3000
|
||||
CMD('Deploy [Hold Position]', troopsRoot, function() self:UnloadTroops(group, { behavior = 'defend' }) end)
|
||||
@ -1538,7 +1623,14 @@ function CTLD:BuildGroupMenus(group)
|
||||
end
|
||||
if bestName and bestMeta then
|
||||
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' })
|
||||
else
|
||||
_msgGroup(group, 'No friendly crates found to mark.')
|
||||
@ -1552,7 +1644,8 @@ function CTLD:BuildGroupMenus(group)
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
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
|
||||
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)
|
||||
@ -1705,9 +1798,9 @@ function CTLD:BuildGroupMenus(group)
|
||||
if self._getZoneCenterAndRadius then center = select(1, self:_getZoneCenterAndRadius(bestZone)) end
|
||||
if not center then
|
||||
local v3 = bestZone:GetPointVec3()
|
||||
center = { x = v3.x, z = v3.z }
|
||||
center = { x = v3.x, y = v3.y or 0, z = v3.z }
|
||||
else
|
||||
center = { x = center.x, z = center.z }
|
||||
center = { x = center.x, y = center.y or 0, z = center.z }
|
||||
end
|
||||
|
||||
-- Choose smoke color per kind (fallbacks if not configured)
|
||||
@ -1716,7 +1809,7 @@ function CTLD:BuildGroupMenus(group)
|
||||
or trigger.smokeColor.White
|
||||
|
||||
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()))
|
||||
else
|
||||
_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
|
||||
local p = unit:GetPointVec3()
|
||||
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 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 nearby = self:GetNearbyCrates(here, radius)
|
||||
local filtered = {}
|
||||
@ -2001,9 +2094,9 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
||||
|
||||
local p = unit:GetPointVec3()
|
||||
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 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 nearby = self:GetNearbyCrates(here, radius)
|
||||
local filtered = {}
|
||||
@ -2197,7 +2290,7 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
||||
-- Verify counts and build
|
||||
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
|
||||
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 })
|
||||
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
|
||||
@ -2230,7 +2323,7 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
|
||||
-- single-key
|
||||
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
|
||||
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 })
|
||||
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
|
||||
@ -2638,7 +2731,13 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
|
||||
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
|
||||
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
|
||||
else
|
||||
-- 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 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
|
||||
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 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 nearby = self:GetNearbyCrates(here, radius)
|
||||
-- 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
|
||||
end
|
||||
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 })
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
@ -2944,7 +3043,7 @@ function CTLD:BuildAtGroup(group, opts)
|
||||
fobBlocked = true
|
||||
else
|
||||
-- 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 })
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
@ -3077,6 +3176,10 @@ function CTLD:DropLoadedCrates(group, howMany)
|
||||
end
|
||||
local p = unit:GetPointVec3()
|
||||
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 requested = (howMany and howMany > 0) and howMany or initialTotal
|
||||
local toDrop = math.min(requested, initialTotal)
|
||||
@ -3094,8 +3197,8 @@ function CTLD:DropLoadedCrates(group, howMany)
|
||||
for i=1,dropNow do
|
||||
local cname = string.format('CTLD_CRATE_%s_%d', k, math.random(100000,999999))
|
||||
local cat = self.Config.CrateCatalog[k]
|
||||
_spawnStaticCargo(self.Side, here, (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 } }
|
||||
_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 = dropPt.x, z = dropPt.z } }
|
||||
lc.byKey[k] = lc.byKey[k] - 1
|
||||
if lc.byKey[k] <= 0 then lc.byKey[k] = nil end
|
||||
lc.total = lc.total - 1
|
||||
@ -3182,7 +3285,7 @@ function CTLD:ScanHoverPickup()
|
||||
|
||||
-- Precision phase
|
||||
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 dz = (bestMeta.point.z - p3.z)
|
||||
local right, fwd = _projectToBodyFrame(dx, dz, hdg)
|
||||
@ -3363,12 +3466,16 @@ function CTLD:LoadTroops(group, opts)
|
||||
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] = {
|
||||
count = capacity,
|
||||
typeKey = 'RIFLE',
|
||||
count = #unitsList,
|
||||
typeKey = requestedType,
|
||||
}
|
||||
_eventSend(self, group, nil, 'troops_loaded', { count = capacity })
|
||||
_eventSend(self, group, nil, 'troops_loaded', { count = #unitsList })
|
||||
end
|
||||
|
||||
function CTLD:UnloadTroops(group, opts)
|
||||
@ -3392,18 +3499,22 @@ function CTLD:UnloadTroops(group, opts)
|
||||
end
|
||||
local p = unit:GetPointVec3()
|
||||
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
|
||||
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
|
||||
-- Spawn a simple infantry fireteam
|
||||
-- Build the unit composition based on type
|
||||
local comp, _ = self:_resolveTroopUnits(load.typeKey)
|
||||
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, {
|
||||
type = 'Infantry AK', name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
||||
x = center.x + i*1.5, y = center.z + (i%2==0 and 2 or -2), heading = hdg
|
||||
type = tostring(comp[i] or 'Infantry AK'),
|
||||
name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
||||
x = center.x + dx, y = center.z + dz, heading = hdgRad
|
||||
})
|
||||
end
|
||||
local groupData = {
|
||||
@ -3439,6 +3550,27 @@ function CTLD:UnloadTroops(group, opts)
|
||||
end
|
||||
-- #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
|
||||
-- =========================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user