From a3debfe000e544be3b63922cee89850e952bbc73 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Sat, 9 Nov 2024 20:13:16 +0100 Subject: [PATCH] VEAF - AA crates stacking feature: Allows players to create systems using as many crates as they like Example : an amount X of patriot launcher crates allows for Y launchers to be deployed, if a player brings 2*X+Z crates (Z being lower then X), then deploys the patriot site, 2*Y launchers will be in the group and Z launcher crate will be left over --- CTLD.lua | 233 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 168 insertions(+), 65 deletions(-) diff --git a/CTLD.lua b/CTLD.lua index b4969a3..6833ccb 100644 --- a/CTLD.lua +++ b/CTLD.lua @@ -76,9 +76,6 @@ ctld.vehiclesWeight = { ["M1043 HMMWV Armament"] = 2500 } -ctld.aaLaunchers = 3 -- controls how many launchers to add to the kub/buk when its spawned. -ctld.hawkLaunchers = 8 -- controls how many launchers to add to the hawk when its spawned. - ctld.spawnRPGWithCoalition = true --spawns a friendly RPG unit with Coalition forces ctld.spawnStinger = false -- spawns a stinger / igla soldier with a group of 6 or more soldiers! @@ -118,6 +115,9 @@ ctld.hoverTime = 10 -- Time to hold hover above a crate for loading in seconds -- end of Simulated Sling load configuration -- AA SYSTEM CONFIG -- + +ctld.aaLaunchers = 3 -- controls how many launchers to add to the AA systems when its spawned if no amount is specified in the template. + -- Sets a limit on the number of active AA systems that can be built for RED. -- A system is counted as Active if its fully functional and has all parts -- If a system is partially destroyed, it no longer counts towards the total @@ -128,6 +128,11 @@ ctld.AASystemLimitRED = 20 -- Red side limit ctld.AASystemLimitBLUE = 20 -- Blue side limit +-- Allows players to create systems using as many crates as they like +-- Example : an amount X of patriot launcher crates allows for Y launchers to be deployed, if a player brings 2*X+Z crates (Z being lower then X), then deploys the patriot site, 2*Y launchers will be in the group and Z launcher crate will be left over + +ctld.AASystemCrateStacking = false + --END AA SYSTEM CONFIG -- -- ***************** JTAC CONFIGURATION ***************** @@ -702,8 +707,8 @@ ctld.spawnableCrates = { { weight = 540, desc = "HAWK Launcher", unit = "Hawk ln", side = 2}, { weight = 545, desc = "HAWK Search Radar", unit = "Hawk sr", side = 2 }, { weight = 546, desc = "HAWK Track Radar", unit = "Hawk tr", side = 2 }, - { weight = 547, desc = "HAWK PCP", unit = "Hawk pcp" , side = 2 }, -- Remove this if on 1.2 - { weight = 548, desc = "HAWK CWAR", unit = "Hawk cwar" , side = 2 }, -- Remove this if on 2.5 + --{ weight = 547, desc = "HAWK PCP", unit = "Hawk pcp" , side = 2 }, -- Remove this if on 1.2 + --{ weight = 548, desc = "HAWK CWAR", unit = "Hawk cwar" , side = 2 }, -- Remove this if on 2.5 { weight = 549, desc = "HAWK Repair", unit = "HAWK Repair" , side = 2 }, -- End of HAWK @@ -722,12 +727,12 @@ ctld.spawnableCrates = { }, ["AA long range"] = { -- Patriot System - { weight = 555, desc = "Patriot Launcher", unit = "Patriot ln", side = 2 }, + { weight = 555, desc = "Patriot Launcher", unit = "Patriot ln", side = 2, cratesRequired = 2}, { weight = 556, desc = "Patriot Radar", unit = "Patriot str" , side = 2 }, { weight = 557, desc = "Patriot ECS", unit = "Patriot ECS", side = 2 }, -- { weight = 553, desc = "Patriot ICC", unit = "Patriot cp", side = 2 }, -- { weight = 554, desc = "Patriot EPP", unit = "Patriot EPP", side = 2 }, - { weight = 558, desc = "Patriot AMG (optional)", unit = "Patriot AMG" , side = 2 }, + --{ weight = 558, desc = "Patriot AMG (optional)", unit = "Patriot AMG" , side = 2 }, { weight = 559, desc = "Patriot Repair", unit = "Patriot Repair" , side = 2 }, -- End of Patriot @@ -1598,6 +1603,8 @@ end --- Tells CTLD What multipart AA Systems there are and what parts they need -- A New system added here also needs the launcher added +-- The number of times that each part is spawned for each system is specified by the entry "amount", NOTE : they will be spawned in a circle with the corresponding headings, NOTE 2 : launchers will use the default ctld.aaLauncher amount if nothing is specified +-- If a component does not require a crate, it can be specified via the entry "NoCrate" set to true ctld.AASystemTemplate = { { @@ -1605,10 +1612,10 @@ ctld.AASystemTemplate = { count = 4, parts = { {name = "Hawk ln", desc = "HAWK Launcher", launcher = true}, - {name = "Hawk tr", desc = "HAWK Track Radar"}, - {name = "Hawk sr", desc = "HAWK Search Radar"}, - {name = "Hawk pcp", desc = "HAWK PCP"}, - {name = "Hawk cwar", desc = "HAWK CWAR"}, + {name = "Hawk tr", desc = "HAWK Track Radar", amount = 2}, + {name = "Hawk sr", desc = "HAWK Search Radar", amount = 2}, + {name = "Hawk pcp", desc = "HAWK PCP", NoCrate = true}, + {name = "Hawk cwar", desc = "HAWK CWAR", amount = 2, NoCrate = true}, }, repair = "HAWK Repair", }, @@ -1616,9 +1623,10 @@ ctld.AASystemTemplate = { name = "Patriot AA System", count = 4, parts = { - {name = "Patriot ln", desc = "Patriot Launcher", launcher = true}, + {name = "Patriot ln", desc = "Patriot Launcher", launcher = true, amount = 8}, {name = "Patriot ECS", desc = "Patriot Control Unit"}, - {name = "Patriot str", desc = "Patriot Search and Track Radar"}, + {name = "Patriot str", desc = "Patriot Search and Track Radar", amount = 2}, + {name = "Patriot AMG", desc = "Patriot AMG DL relay", NoCrate = true}, }, repair = "Patriot Repair", }, @@ -4046,78 +4054,155 @@ end function ctld.unpackAASystem(_heli, _nearestCrate, _nearbyCrates,_aaSystemTemplate) if ctld.rearmAASystem(_heli, _nearestCrate, _nearbyCrates,_aaSystemTemplate) then - -- rearmed hawk + -- rearmed system return end - -- are there all the pieces close enough together local _systemParts = {} --initialise list of parts for _,_part in pairs(_aaSystemTemplate.parts) do - _systemParts[_part.name] = {name = _part.name,desc = _part.desc,found = false} + local _systemPart = {name = _part.name, desc = _part.desc, launcher = _part.launcher, amount = _part.amount, NoCrate = _part.NoCrate, found = 0, required = 1} + -- if the part is a NoCrate required, it's found by default + if _systemPart.NoCrate ~= nil then + _systemPart.found = 1 + end + _systemParts[_part.name] = _systemPart end - -- find all nearest crates and add them to the list if they're part of the AA System + local _cratePositions = {} + local _crateHdg = {} + + local crateDistance = 500 + + -- find all crates close enough and add them to the list if they're part of the AA System for _, _nearbyCrate in pairs(_nearbyCrates) do + if _nearbyCrate.dist < crateDistance then - if _nearbyCrate.dist < 500 then + local _name = _nearbyCrate.details.unit - if _systemParts[_nearbyCrate.details.unit] ~= nil and _systemParts[_nearbyCrate.details.unit].found == false then - local _foundPart = _systemParts[_nearbyCrate.details.unit] + if _systemParts[_name] ~= nil then - _foundPart.found = true - _foundPart.crate = _nearbyCrate + local foundCount = _systemParts[_name].found - _systemParts[_nearbyCrate.details.unit] = _foundPart + -- if this is our first time encountering this part of the system + if foundCount == 0 then + local _foundPart = _systemParts[_name] + + _foundPart.found = 1 + _foundPart.crates = {} + + -- store the number of crates required to compute how many crates will have to be removed later and to see if the system can be deployed + local cratesRequired = _nearbyCrate.details.cratesRequired + if cratesRequired ~= nil then + _foundPart.required = cratesRequired + end + + _systemParts[_name] = _foundPart + _cratePositions[_name] = {} + _crateHdg[_name] = {} + else + -- otherwise, we found another crate for the same part + _systemParts[_name].found = foundCount + 1 + end + + -- add the crate to the part info along with it's position and heading + local crateUnit = _nearbyCrate.crateUnit + table.insert(_systemParts[_name].crates, _nearbyCrate) + table.insert(_cratePositions[_name], crateUnit:getPoint()) + table.insert(_crateHdg[_name], mist.getHeading(crateUnit)) end end end - local spawnDistance = 20 -- radius to spawn launchers in a circle relative to the crate location + -- Compute the centroids for each type of crates and then the centroid of all the system crates which is used to find the spawn location for each part and a position for the NoCrate parts respectively + -- One issue, all crates are considered for the centroid and the headings but not all of them may be used if crate stacking is allowed + local _crateCentroids = {} + local _idxCentroids = {} + for _partName, _partPositions in pairs(_cratePositions) do + _crateCentroids[_partName] = ctld.getCentroid(_partPositions) + table.insert(_idxCentroids, _crateCentroids[_partName]) + end + local _crateCentroid = ctld.getCentroid(_idxCentroids) + + -- Compute the average heading for each type of crates to know the heading to spawn the part + local _aveHdg = {} + -- Headings of each group of crates + for _partName, _crateHeadings in pairs(_crateHdg) do + local crateCount = #_crateHeadings + _aveHdg[_partName] = 0 + -- Heading of each crate within a group + for _index, _crateHeading in pairs(_crateHeadings) do + _aveHdg[_partName] = _crateHeading / crateCount + _aveHdg[_partName] + end + end + + local spawnDistance = 50 -- circle diameter to spawn units in a circle and randomize position relative to the crate location + local arcRad = math.pi * 2 + local _txt = "" local _posArray = {} - local _typeArray = {} local _hdgArray = {} + local _typeArray = {} + -- for each part of the system parts for _name, _systemPart in pairs(_systemParts) do - if _systemPart.found == false then + -- check if enough crates were found to build the part + if _systemPart.found < _systemPart.required then _txt = _txt.."Missing ".._systemPart.desc.."\n" else + -- use the centroid of the crates for this part as a spawn location + local _point = _crateCentroids[_name] + -- in the case this centroid does not exist (NoCrate), use the centroid of all crates found and add some randomness + if _point == nil then + _point = _crateCentroid + _point = { x = _point.x + math.random(0,3)*spawnDistance, y = _point.y, z = _point.z + math.random(0,3)*spawnDistance} + end - local _launcherPart = ctld.getLauncherUnitFromAATemplate(_aaSystemTemplate) - local _point = _systemPart.crate.crateUnit:getPoint() - local _hdg = mist.getHeading(_systemPart.crate.crateUnit, true) + -- use the average heading to spawn the part at + local _hdg = _aveHdg[_name] + -- if non are found (NoCrate), random heading + if _hdg == nil then + _hdg = math.random(0, arcRad) + end - --handle multiple launchers from one crate - if (_name == "Hawk ln" and ctld.hawkLaunchers > 1) - or (_launcherPart == _name and ctld.aaLaunchers > 1) then - - --add multiple launcher - local _launchers = ctld.aaLaunchers - - if _name == "Hawk ln" then - _launchers = ctld.hawkLaunchers + -- search for the amount of times this part needs to be spawned, by default 1 for any unit and aaLaunchers for launchers + local partAmount = 1 + if _systemPart.amount == nil then + if _systemPart.launcher ~= nil then + partAmount = ctld.aaLaunchers end + else + -- but the amount may also be specified in the template + partAmount = _systemPart.amount + end + -- if crate stacking is allowed, then find the multiplication factor for the amount depending on how many crates are required and how many were found + if ctld.AASystemCrateStacking then + _systemPart.amountFactor = _systemPart.found - _systemPart.found%_systemPart.required + else + _systemPart.amountFactor = 1 + end + partAmount = partAmount * _systemPart.amountFactor - for _i = 1, _launchers do + --handle multiple units per part by spawning them in a circle around the crate + if partAmount > 1 then + + local angular_step = arcRad / partAmount - -- spawn in a circle around the crate - local _angle = math.pi * 2 * (_i - 1) / _launchers + for _i = 1, partAmount do + local _angle = (angular_step * (_i - 1) + _hdg)%arcRad local _xOffset = math.cos(_angle) * spawnDistance local _yOffset = math.sin(_angle) * spawnDistance - local lnPoint = { x = _point.x + _xOffset, y = _point.y, z = _point.z + _yOffset } - - table.insert(_posArray, lnPoint) + table.insert(_posArray, { x = _point.x + _xOffset, y = _point.y, z = _point.z + _yOffset }) + table.insert(_hdgArray, _angle) -- also spawn them perpendicular to that point of the circle table.insert(_typeArray, _name) - table.insert(_hdgArray, _hdg) end else table.insert(_posArray, _point) - table.insert(_typeArray, _name) table.insert(_hdgArray, _hdg) + table.insert(_typeArray, _name) end end end @@ -4140,17 +4225,32 @@ function ctld.unpackAASystem(_heli, _nearestCrate, _nearbyCrates,_aaSystemTempla -- destroy crates for _name, _systemPart in pairs(_systemParts) do + -- if there is a crate to delete in the first place + if _systemPart.NoCrate ~= true then + -- figure out how many crates to delete since we searched for as many as possible, not all of them might have been used + local amountToDel = _systemPart.amountFactor*_systemPart.required + local DelCounter = 0 - if _heli:getCoalition() == 1 then - ctld.spawnedCratesRED[_systemPart.crate.crateUnit:getName()] = nil - else - ctld.spawnedCratesBLUE[_systemPart.crate.crateUnit:getName()] = nil + -- for each crate found for this part + for _index, _crate in pairs(_systemPart.crates) do + -- if we still need to delete some crates + if DelCounter < amountToDel then + if _heli:getCoalition() == 1 then + ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil + else + ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil + end + + --destroy + -- if ctld.slingLoad == false then + _crate.crateUnit:destroy() + DelCounter = DelCounter + 1 -- count up for one more crate has been deleted + --end + else + break + end + end end - - --destroy - -- if ctld.slingLoad == false then - _systemPart.crate.crateUnit:destroy() - --end end -- HAWK / BUK READY! @@ -4239,13 +4339,13 @@ function ctld.repairAASystem(_heli, _nearestCrate,_aaSystem) --spawn new one local _types = {} - local _points = {} local _hdgs = {} + local _points = {} for _, _part in pairs(_oldHawk) do table.insert(_points, _part.point) - table.insert(_types, _part.unit) table.insert(_hdgs, _part.hdg) + table.insert(_types, _part.unit) end --remove old system @@ -4358,15 +4458,18 @@ function ctld.spawnCrateGroup(_heli, _positions, _types, _hdgs) ["task"] = {}, } - local defaultHdg = 120 * math.pi / 180 -- radians = 120 degrees + local _hdg = 120 * math.pi / 180 -- radians = 120 degrees + local _spreadMin = 2 + local _spreadMax = 5 + local _spreadMult = 2 for _i, _pos in ipairs(_positions) do local _unitId = ctld.getNextUnitId() local _details = { type = _types[_i], unitId = _unitId, name = string.format("Unpacked %s #%i", _types[_i], _unitId) } if _hdgs and _hdgs[_i] then - defaultHdg = _hdgs[_i] + _hdg = _hdgs[_i] end - _group.units[_i] = ctld.createUnit(_pos.x + 5, _pos.z + 5, defaultHdg, _details) + _group.units[_i] = ctld.createUnit(_pos.x +math.random(_spreadMin,_spreadMax)*_spreadMult, _pos.z +math.random(_spreadMin,_spreadMax)*_spreadMult, _hdg, _details) end --mist function @@ -5320,7 +5423,7 @@ function ctld.addJTACRadioCommand(_side) local jtacCounter = 0 for _jtacGroupName,jtacUnit in pairs(ctld.jtacUnits) do - ctld.logTrace(string.format("JTAC - MENU - [%s] - processing menu", ctld.p(_jtacGroupName))) + --ctld.logTrace(string.format("JTAC - MENU - [%s] - processing menu", ctld.p(_jtacGroupName))) --if the JTAC is on the same team as the group being considered local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side @@ -5329,8 +5432,8 @@ function ctld.addJTACRadioCommand(_side) if ctld.jtacGroupSubMenuPath[_jtacGroupName] and #ctld.jtacGroupSubMenuPath[_jtacGroupName]==2 then missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName]) end - ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacTargetsList = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName]))) - ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacCurrentTargets = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacCurrentTargets[_jtacGroupName]))) + --ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacTargetsList = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName]))) + --ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacCurrentTargets = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacCurrentTargets[_jtacGroupName]))) local jtacActionMenu = false for _,_specialOptionTable in pairs(ctld.jtacSpecialOptions) do @@ -5353,7 +5456,7 @@ function ctld.addJTACRadioCommand(_side) end --add the JTAC group submenu to the current page ctld.jtacGroupSubMenuPath[_jtacGroupName] = missionCommands.addSubMenuForGroup(_groupId, jtacGroupSubMenuName, jtacCurrentPagePath) - ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacGroupSubMenuPath = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName]))) + --ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacGroupSubMenuPath = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName]))) --make a copy of the JTAC group submenu's path to insert the target's list on as many pages as required. The JTAC's group submenu path only leads to the first page local jtacTargetPagePath = mist.utils.deepCopy(ctld.jtacGroupSubMenuPath[_jtacGroupName]) @@ -5395,7 +5498,7 @@ function ctld.addJTACRadioCommand(_side) end if #ctld.jtacTargetsList[_jtacGroupName] >= 1 then - ctld.logTrace(string.format("JTAC - MENU - [%s] - adding targets menu", ctld.p(_jtacGroupName))) + --ctld.logTrace(string.format("JTAC - MENU - [%s] - adding targets menu", ctld.p(_jtacGroupName))) --add a reset targeting option to revert to automatic JTAC unit targeting missionCommands.addCommandForGroup(_groupId, "Reset TGT Selection", jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = nil})