Version 2.2.4

Slotty and friends
This commit is contained in:
Christian Franz 2024-05-23 14:14:58 +02:00
parent 07a32bd051
commit 8b4080c218
20 changed files with 511 additions and 89 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "2.1.0"
FARPZones.version = "2.1.1"
FARPZones.verbose = false
--[[--
Version History
@ -24,7 +24,7 @@ FARPZones.verbose = false
2.0.2 - clean-up
verbosity enhancements
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
--]]--
@ -547,6 +547,8 @@ function FARPZones.loadMission()
local theAB = theFARP.mainFarp
theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
--[[--
local groupData = fData.defenderData
if groupData and #groupData.units > 0 then
local cty = groupData.cty
@ -560,11 +562,13 @@ function FARPZones.loadMission()
local cat = groupData.cat
theFARP.resources = coalition.addGroup(cty, cat, groupData)
end
--]]--
FARPZones.produceVehicles(theFARP) -- do full defender and resource cycle
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map
if (not theFARP.defenders) and (not theFARP.resources) then
-- if (not theFARP.defenders) and (not theFARP.resources) then
-- we instigate a resource and defender drop
FARPZones.produceVehicles(theFARP)
end
-- FARPZones.produceVehicles(theFARP)
-- end
else
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {}
cfxSSBClient.version = "4.0.0"
cfxSSBClient.version = "4.0.1"
cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -16,6 +16,8 @@ cfxSSBClient.requiredLibs = {
Version History
4.0.0 - dmlZones
- cfxMX instead of cfxGroups
4.0.1 - check slot availability immediately upon start
- ssb autoenable option
--]]--
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
@ -490,33 +492,31 @@ function cfxSSBClient.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("SSBClientConfig")
if not theZone then
trigger.action.outText("+++SSBC: no config zone!", 30)
return
theZone = cfxZones.createSimpleZone("SSBClientConfig")
end
trigger.action.outText("+++SSBC: found config zone!", 30)
cfxSSBClient.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxSSBClient.verbose = theZone.verbose
-- single-use
cfxSSBClient.singleUse = cfxZones.getBoolFromZoneProperty(theZone, "singleUse", false) -- use airframes only once? respawn after kick must be disabled in ssb
cfxSSBClient.reUseAfter = cfxZones.getNumberFromZoneProperty(theZone, "reUseAfter", -1)
cfxSSBClient.singleUse = theZone:getBoolFromZoneProperty("singleUse", false) -- use airframes only once? respawn after kick must be disabled in ssb
cfxSSBClient.reUseAfter = theZone:getNumberFromZoneProperty( "reUseAfter", -1)
-- airfield availability
cfxSSBClient.allowNeutralFields = cfxZones.getBoolFromZoneProperty(theZone, "allowNeutralFields", false)
cfxSSBClient.allowNeutralFields = theZone:getBoolFromZoneProperty( "allowNeutralFields", false)
cfxSSBClient.maxAirfieldRange = cfxZones.getNumberFromZoneProperty(theZone, "maxAirfieldRange", 3000) -- meters, to find attached airfield
cfxSSBClient.maxAirfieldRange = theZone:getNumberFromZoneProperty("maxAirfieldRange", 3000) -- meters, to find attached airfield
-- optimization
cfxSSBClient.keepInAirGroups = cfxZones.getBoolFromZoneProperty(theZone, "keepInAirGroups", false)
cfxSSBClient.keepInAirGroups = theZone:getBoolFromZoneProperty("keepInAirGroups", false)
-- SSB direct control.
-- USE ONLY WHEN YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
cfxSSBClient.enabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "enabledFlagValue", 0)
cfxSSBClient.enabledFlagValue = theZone:getNumberFromZoneProperty("enabledFlagValue", 0)
cfxSSBClient.disabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "disabledFlagValue", cfxSSBClient.enabledFlagValue + 100)
cfxSSBClient.disabledFlagValue = theZone:getNumberFromZoneProperty("disabledFlagValue", cfxSSBClient.enabledFlagValue + 100)
cfxSSBClient.ssbAutoenable = theZone:getBoolFromZoneProperty("ssbAutoenable", true)
end
--
@ -601,13 +601,18 @@ function cfxSSBClient.start()
-- install a timed update just to make sure
-- and start NOW
timer.scheduleFunction(cfxSSBClient.update, {}, timer.getTime() + 1)
-- timer.scheduleFunction(cfxSSBClient.update, {}, timer.getTime() + 1)
cfxSSBClient.update()
-- start dml update (on a different timer
cfxSSBClient.dmlUpdate()
-- now turn on ssb
if cfxSSBClient.ssbAutoenable then
trigger.action.setUserFlag("SSB",100)
else
trigger.action.outText("WARNING: cfxSSBClient did !!NOT!! auto-enable SSB for mission.", 30)
end
-- persistence: load states
if persistence then

View File

@ -1,5 +1,5 @@
bombRange = {}
bombRange.version = "1.1.2"
bombRange.version = "1.1.3"
bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = {
@ -21,6 +21,7 @@ VERSION HISTORY
1.1.1 - fixed reading smoke color for zone
minor clean-up
1.1.2 - corrected bug when no bomb range is detected
1.1.3 - added meters/feet distance when reporting impact
--]]--
bombRange.bombs = {} -- live tracking
@ -543,7 +544,8 @@ function bombRange.impacted(weapon, target, finalPass)
tDist = math.floor(tDist*100) /100
trigger.action.outTextForGroup(weapon.gID, "impact of " .. weapon.type .. " released by " .. weapon.pName .. " from " .. weapon.uType .. " after traveling " .. tDist .. " km in " .. t .. " sec, impact velocity at impact is " .. v .. " m/s!", 30)
end
local meters = math.floor(minDist * 10) / 10
local feet = math.floor(minDist * 3.28084 * 10) / 10
local msg = ""
if impactInside then
local percentage = 0
@ -553,15 +555,17 @@ function bombRange.impacted(weapon, target, finalPass)
percentage = 1 - (minDist / theRange.radius)
percentage = math.floor(percentage * 100)
end
msg = "INSIDE target area"
if theRange.reportName then msg = msg .. " " .. theRange.name end
if (not targetName) and theRange.details then msg = msg .. ", off-center by " .. math.floor(minDist *10)/10 .. " m" end
if (not targetName) and theRange.details then msg = msg .. ", off-center by " .. meters .. "m/" .. feet .. "ft" end--math.floor(minDist *10)/10 .. " m" end
if targetName then msg = msg .. ", hit on " .. targetName end
if not theRange.usePercentage then
percentage = 100
else
msg = msg .. " (Quality " .. percentage .."%)"
msg = msg .. " (Quality " .. percentage .."%)" --, off-center by " .. meters .. "m/" .. feet .. "ft)"
end
if theRange.hitOut then
@ -572,7 +576,7 @@ function bombRange.impacted(weapon, target, finalPass)
else
msg = "Outside target area"
if theRange.reportName then msg = msg .. " " .. theRange.name end
if theRange.details then msg = msg .. " (off-center by " .. math.floor(minDist *10)/10 .. " m)" end
if theRange.details then msg = msg .. " (off-center by " .. meters .. "m/" .. feet .. "ft)" end --math.floor(minDist *10)/10 .. " m)" end
msg = msg .. ", no hit."
bombRange.addImpactForWeapon(weapon, false, 0)
end

View File

@ -4,7 +4,7 @@
-- *** EXTENDS ZONES: 'pathing' attribute
--
cfxCommander = {}
cfxCommander.version = "1.1.4"
cfxCommander.version = "2.0.0"
--[[-- VERSION HISTORY
- 1.0.5 - createWPListForGroupToPointViaRoads: detect no road found
- 1.0.6 - build in more group checks in assign wp list
@ -30,7 +30,11 @@ cfxCommander.version = "1.1.4"
- 1.1.3 - isExist() guard improvements for multiple methods
- cleaned up comments
- 1.1.4 - hardened makeGroupGoThere()
- 2.0.0 - dml zones
- units now can move with moveFormation
- hardened performCommands()
- createWPListForGroupToPoint() supports moveFormation
- makeGroupGoTherePreferringRoads() supports moveFormation
--]]--
cfxCommander.requiredLibs = {
@ -72,16 +76,11 @@ function cfxCommander.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("CommanderConfig")
if not theZone then
trigger.action.outText("+++cmdr: no config zone!", 30)
return
theZone = cfxZones.createSimpleZone("CommanderConfig")
end
trigger.action.outText("+++cmdr: found config zone!", 30)
cfxCommander.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxCommander.forceOffRoad = cfxZones.getBoolFromZoneProperty(theZone, "forceOffRoad", false) -- if true, vehicles path follow roads, but may drive offroad
cfxCommander.noRoadsAtAll = cfxZones.getBoolFromZoneProperty(theZone, "noRoadsAtAll", false)
cfxCommander.verbose = theZone.verbose
cfxCommander.forceOffRoad = theZone:getBoolFromZoneProperty("forceOffRoad", false) -- if true, vehicles path follow roads, but may drive offroad
cfxCommander.noRoadsAtAll = theZone:getBoolFromZoneProperty("noRoadsAtAll", false)
end
--
@ -118,8 +117,14 @@ function cfxCommander.performCommands(commandData)
if not commandData.group then
commandData.group = Group.getByName(commandData.name) -- better be inited!
end
if not Group.isExist(commandData.group) then
-- something bad is happening
return nil
end
-- get the AI
local theController = commandData.group:getController()
if not theController then return nil end
for i=1, #commandData.commands do
if cfxCommander.verbose then
trigger.action.outText("Commander: performing " .. commandData.commands[i].id, 30)
@ -204,7 +209,6 @@ function cfxCommander.doScheduledTask(data)
local theGroup = data.group
if not theGroup then return end
if not Group.isExist(theGroup) then return end
-- if not theGroup.isExist then return end
local theController = theGroup:getController()
theController:pushTask(data.task)
@ -262,7 +266,7 @@ function cfxCommander.createBasicWaypoint(point, speed, formation)
if not formation then formation = "Off Road" end
-- legal formations:
-- Off road
-- Off Road
-- On Road -- second letter upper case?
-- Cone
-- Rank
@ -300,21 +304,22 @@ function cfxCommander.assignWPListToGroup(group, wpList, delay)
local theTask = cfxCommander.buildTaskFromWPList(wpList)
local ctrl = group:getController()
--[[--
if delay < 0.001 then -- immediate action
if ctrl then
ctrl:setTask(theTask)
end
else
-- delay execution of this command by the specified amount
-- of seconds
cfxCommander.scheduleTaskForGroup(group, theTask, delay)
end
--]]--
cfxCommander.scheduleTaskForGroup(group, theTask, delay)
end
function cfxCommander.createWPListForGroupToPoint(group, point, speed, formation)
--[[--
Formations and their "action" keywords
Line Abreast = "Rank"
Cone = "Cone"
Vee = "Vee"
Diamond = "Diamond"
Echelon Left = "EchelonL"
Echelon Right = "EchelonR"
Custom = "Custom"
--]]--
function cfxCommander.createWPListForGroupToPoint(group, point, speed, moveFormation)
if type(group) == 'string' then -- group name
group = Group.getByName(group)
end
@ -323,8 +328,8 @@ function cfxCommander.createWPListForGroupToPoint(group, point, speed, formation
-- here we are, and we want to go there. In DCS, this means that
-- we need to create a wp list consisting of here and there
local here = dcsCommon.getGroupLocation(group)
local wpHere = cfxCommander.createBasicWaypoint(here, speed, formation)
local wpThere = cfxCommander.createBasicWaypoint(point, speed, formation)
local wpHere = cfxCommander.createBasicWaypoint(here, speed, moveFormation)
local wpThere = cfxCommander.createBasicWaypoint(point, speed, moveFormation)
wpList[1] = wpHere
wpList[2] = wpThere
return wpList
@ -398,12 +403,9 @@ function cfxCommander.createWPListForGroupToPointViaRoads(group, point, speed)
if pathLength > (2 * direct) then
-- road takes too long, take direct approach
--trigger.action.outText("+++ road path (" .. pathLength .. ") > twice direct route(" .. direct .. "), commencing direct off-road", 30)
return cfxCommander.createWPListForGroupToPoint(group, point, speed)
end
--trigger.action.outText("+++ ".. group:getName() .. ": choosing road path l=" .. pathLength .. " over direct route d=" .. direct, 30)
-- if we are here, the road trip is valid
for idx, wp in pairs(rawRoadPoints) do
-- createBasic... supports w.xy format
@ -422,16 +424,17 @@ function cfxCommander.createWPListForGroupToPointViaRoads(group, point, speed)
return wpList
end
function cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, delay)
function cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, delay, moveFormation)
if type(group) == 'string' then -- group name
group = Group.getByName(group)
end
if not delay then delay = 0 end
if not moveFormation then moveFormation = "Off Road" end
if cfxCommander.noRoadsAtAll then
-- we don't even follow roads, completely forced off
cfxCommander.makeGroupGoThere(group, there, speed, "Off Road", delay)
cfxCommander.makeGroupGoThere(group, there, speed, moveFormation, delay)
return
end
@ -443,7 +446,7 @@ function cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, delay
local oRide = cfxCommander.hasPathZoneFor(here, there)
if oRide and oRide.pathing == "offroad" then
-- yup, override road preference
cfxCommander.makeGroupGoThere(group, there, speed, "Off Road", delay)
cfxCommander.makeGroupGoThere(group, there, speed, moveFormation, delay)
return
end
end
@ -498,9 +501,6 @@ function cfxCommander.start()
-- identify and process all 'pathing' zones
local pathZones = cfxZones.getZonesWithAttributeNamed("pathing")
-- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not
-- paused
for k, aZone in pairs(pathZones) do
cfxCommander.processPathingZone(aZone) -- process attribute and add to zone
cfxCommander.addPathingZone(aZone) -- remember it so we can smoke it

View File

@ -1075,7 +1075,7 @@ function csarManager.updateCSARMissions()
else
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
trigger.action.outTextForCoalition(aMission.side, msg, 30)
trigger.action.outSoundForCoalition(aMission.side, csarManager.actionSound)
trigger.action.outSoundForCoalition(aMission.side, csarManager.lostSound)
csarManager.invokeCallbacks(aMission.side, false, 1, "KIA", aMission)
end
end
@ -1765,4 +1765,6 @@ end
-- may want to change if time limit was exceeded on return to tell
player that they did not survive the transport
- randomize smoke color if smoke color has more than one entries
--]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.0.6"
dcsCommon.version = "3.0.7"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option

View File

@ -1,6 +1,6 @@
cfxGroundTroops = {}
cfxGroundTroops.version = "2.0.1"
cfxGroundTroops.ups = 1
cfxGroundTroops.version = "2.2.0"
cfxGroundTroops.ups = 0.25 -- every 4 seconds
cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
@ -29,10 +29,11 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
2.0.0 - dmlZones
- jtacSound
- clanup
- cleanup
- jtacVerbose
2.0.1 - small fiex ti checkPileUp()
2.1.0 - captureandhold - oneshot attackowned
2.2.0 - moveFormation support
an entry into the deployed troop table has the following attributes
- group - the group
@ -41,6 +42,8 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
"laze" - will stay in place and try to laze visible vehicles in range
"attackOwnedZone" - interface to cfxOwnedZones module, seeks out
enemy zones to attack and capture them
"captureandhold" - interface to ownedZones, seeks out nearest enemy
or neutral owned zone. once captured, it stays there
"wait-<some other orders>" do nothing. the "wait" prefix will be removed some time and <some other order> then revealed. Used at least by heloTroops
"train" - target dummies. ROE=HOLD, no ground loop
"attack" - transition to destination, once there, stop and
@ -53,6 +56,7 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
- lazeTarget - target currently lazing
- lazeCode - laser code. default is 1688
- moving - has been given orders to move somewhere already. used for first movement order with attack orders
-- reduced ups to 0.24, updating troops every 4 seconds is fast enough
usage:
@ -169,9 +173,9 @@ function cfxGroundTroops.makeTroopsEngageEnemies(troop)
-- we lerp to 2/3 of enemy location
there = dcsCommon.vLerp(from, there, 0.66)
local moveFormation = troop.moveFormation
local speed = 10 -- m/s = 10 km/h -- wait. 10 m/s is 36 km/h
cfxCommander.makeGroupGoThere(group, there, speed)
cfxCommander.makeGroupGoThere(group, there, speed, moveFormation)
local attask = cfxCommander.createAttackGroupCommand(enemies)
cfxCommander.scheduleTaskForGroup(group, attask, 0.5)
troop.moving = true
@ -189,15 +193,20 @@ function cfxGroundTroops.makeTroopsEngageZone(troop)
local enemyZone = troop.destination -- must be cfxZone
local from = dcsCommon.getGroupLocation(group)
if not from then return end -- the group died
local there = enemyZone.point -- access zone position
local there = enemyZone:getPoint() -- access zone position
if not there then return end
local speed = 14 -- m/s; 10 m/s = 36 km/h
-- make troops stop in 1 second, then start in 5 seconds to give AI respite
cfxCommander.makeGroupHalt(group, 1) -- 1 second delay
cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, 5)
if troop.orders == "captureandhold" then
-- direct capture never uses roads
cfxCommander.makeGroupGoThere(group, there, speed, troop.moveFormation, 5)
else
-- when we attack any owned zone, we prefer roads
cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, 5, troop.moveFormation)
end
-- remember that we have issued a move order
troop.moving = true
end
@ -236,6 +245,10 @@ end
-- are heading for is already owned by their side, then look for
-- the closest enemy zone, and cut attack orders to move there
function cfxGroundTroops.getClosestEnemyZone(troop)
if not cfxOwnedZones then
trigger.action.outText("+++groundT: WARNING! ownedZones is not loaded, which is required.", 30)
return nil
end
local p = dcsCommon.getGroupLocation(troop.group)
local tempZone = cfxZones.createSimpleZone("tz", p, 100)
tempZone.owner = troop.side
@ -251,6 +264,17 @@ function cfxGroundTroops.updateZoneAttackers(troop)
end
troop.insideDestination = false -- mark as not inside
-- we *have* a destination, but not yet isued move orders,
-- meaning that we just spawned, probably from helo.
-- do not look for new location, issue move orders instead
if not troop.hasMovedOrders and troop.destination then
troop.hasMovedOrders = true
cfxGroundTroops.makeTroopsEngageZone(troop)
troop.lastOrderDate = timer.getTime()
troop.speedWarning = 0
return
end
local newTargetZone = cfxGroundTroops.getClosestEnemyZone(troop)
if not newTargetZone then
-- all target zones are friendly, go to guard mode
@ -259,6 +283,12 @@ function cfxGroundTroops.updateZoneAttackers(troop)
end
if newTargetZone ~= troop.destination then
if troop.destination and troop.orders == "captureandhold" then
troop.lastOrderDate = timer.getTime() -- we may even dismiss them
-- from troop array. But orders should remain when picked up by helo
-- we never change target. Stay.
return
end
troop.destination = newTargetZone
cfxGroundTroops.makeTroopsEngageZone(troop)
troop.lastOrderDate = timer.getTime()
@ -532,6 +562,10 @@ function cfxGroundTroops.updateWait(troop)
end
function cfxGroundTroops.updateTroops(troop)
if cfxGroundTroops.verbose then
trigger.action.outText("+++GTroop: enter updateTroopps for <" .. troop.name .. ">", 30)
end
-- if orders start with "wait-" then the troops
-- simply do nothing
if dcsCommon.stringStartsWith(troop.orders, "wait-") then
@ -547,6 +581,9 @@ function cfxGroundTroops.updateTroops(troop)
elseif troop.orders == "attackownedzone" then
cfxGroundTroops.updateZoneAttackers(troop)
elseif troop.orders == "captureandhold" then
cfxGroundTroops.updateZoneAttackers(troop)
elseif troop.orders == "laze" then
cfxGroundTroops.updateLaze(troop)
@ -880,14 +917,16 @@ end
-- createGroundTroop
-- use this to create a cfxGroundTroops from a dcs group
--
function cfxGroundTroops.createGroundTroops(inGroup, range, orders)
function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormation)
local newTroops = {}
if not orders then
orders = "guard"
end
if not moveFormation then moveFormation = "Custom" end
if orders:lower() == "lase" then
orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right.
end
trigger.action.outText("Enter createGT group <" .. inGroup:getName() .. "> with o=<" .. orders .. ">, mf=<" .. moveFormation .. ">", 30)
newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
newTroops.speedWarning = 0
@ -897,6 +936,7 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders)
newTroops.coalition = inGroup:getCoalition()
newTroops.side = newTroops.coalition -- because we'e been using both.
newTroops.name = inGroup:getName()
newTroops.moveFormation = moveFormation
newTroops.moving = false -- set to not have received move orders yet
newTroops.signature = "cfx" -- to verify this is groundTroop group, not dcs groups
if not range then range = 300 end
@ -912,6 +952,7 @@ function cfxGroundTroops.addGroundTroopsToPool(troops) -- troops MUST be a table
end
if not troops.orders then troops.orders = "guard" end
troops.orders = troops.orders:lower()
if not troops.moveFormation then troops.moveFormation = "Custom" end
troops.reschedule = true -- in case we use scheduled update
-- we now add to internal array. this is worked on by all
-- update meths, on scheduled upadtes, it is only used to

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.0.3"
cfxHeloTroops.version = "3.0.4"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -40,6 +40,7 @@ cfxHeloTroops.requestRange = 500 -- meters
3.0.1 - fixed a bug with legalTroops attribute
3.0.2 - fixed a typo in in-air menu
3.0.3 - pointInZone check for insertion rather than radius
3.0.4 - also handles picking up troops with orders "captureandhold"
--]]--
--
@ -52,8 +53,6 @@ cfxHeloTroops.requestRange = 500 -- meters
cfxHeloTroops.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
-- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course
"cfxCommander", -- to make troops do stuff
"cfxGroundTroops", -- generic when dropping troops
@ -641,6 +640,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local orders = conf.troopsOnBoard.orders
local dest = conf.troopsOnBoard.destination
local theName = conf.troopsOnBoard.name
local moveFormation = conf.troopsOnBoard.moveFormation
if not orders then orders = "guard" end
@ -670,7 +670,15 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
troopData.destination = dest -- only for attackzone orders
cfxHeloTroops.deployedTroops[theData.name] = troopData
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation)
if orders == "captureandhold" then
-- we get the target zone NOW!!! before we flip the zone and
-- and make them run to the wrong zone
dest = cfxGroundTroops.getClosestEnemyZone(troop)
troopData.destination = dest
trigger.action.outText("Inserting troops to capture zone <" .. dest.name .. ">", 30)
end
troop.destination = dest -- transfer target zone for attackzone oders
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
@ -735,8 +743,13 @@ function cfxHeloTroops.doLoadGroup(args)
conf.troopsOnBoard.orders = pooledGroup.orders
conf.troopsOnBoard.range = pooledGroup.range
conf.troopsOnBoard.destination = pooledGroup.destination -- may be nil
conf.troopsOnBoard.moveFormation = pooledGroup.moveFormation
if pooledGroup.orders and pooledGroup.orders == "captureandhold" then
conf.troopsOnBoard.destination = nil -- forget last destination so they can be helo-redeployed
end
cfxGroundTroops.removeTroopsFromPool(pooledGroup)
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30)
-- trigger.action.outText("and mf = <" .. conf.troopsOnBoard.moveFormation .. ">", 30)
--trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav")
else
if cfxHeloTroops.verbose then

View File

@ -65,10 +65,12 @@ function income.update()
has, balance = bank.getBalance(1)
tick = string.gsub(income.tickMessage, "<i>", redI)
trigger.action.outTextForCoalition(1, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
trigger.action.outSoundForCoalition(1, income.reportSound)
has, balance = bank.getBalance(2)
tick = string.gsub(income.tickMessage, "<i>", blueI)
trigger.action.outTextForCoalition(2, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
trigger.action.outSoundForCoalition(2, income.reportSound)
end
end
@ -88,6 +90,8 @@ function income.readConfigZone()
income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes
income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available: §<i>")
income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true)
income.reportSound = theZone:getStringFromZoneProperty("reportSound", "<none>")
income.verbose = theZone.verbose
end

254
modules/launchPlatform.lua Normal file
View File

@ -0,0 +1,254 @@
launchPlatform = {}
launchPlatform.version = "0.0.0"
launchPlatform.requiredLibs = {
"dcsCommon",
"cfxZones",
}
launchPlatform.zones = {}
launchPlatform.redLaunchers = {}
launchPlatform.blueLaunchers = {}
-- weapon types currently known
-- 52613349374 = tomahawk
function launchPlatform.addLaunchPlatform(theZone)
launchPlatform.zones[theZone.name] = theZone
if theZone.coa == 1 or theZone.coa == 0 then
launchPlatform.redLaunchers[theZone.name] = theZone
end
if theZone.coa == 2 or theZone.coa == 0 then
launchPlatform.blueLaunchers[theZone.name] = theZone
end
end
function launchPlatform.readLaunchPlatform(theZone)
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.impactRadius = theZone:getNumberFromZoneProperty("radius", 1000)
if theZone:hasProperty("salvos") then
theZone.num = theZone:getNumberFromZoneProperty("salvos", 1)
end
if theZone:hasProperty("salvo") then
theZone.num = theZone:getNumberFromZoneProperty("salvo", 1)
end
-- possible extensions: missile. currently tomahawk launched from a missile cruiser that beams in and vanishes later
-- later versions could support SCUDS and some long-range arty,
-- perhaps even aircraft
end
-- note - the tomahawks don't care who they belong to, we do not
-- need them to belong to anyone, it may be a visibility thing though
function launchPlatform.launchForPlatform(coa, theZone, tgtPoint, tgtZone)
local launchPoint = theZone:createRandomPointInZone()
local gData = launchPlatform.createData(launchPoint, tgtPoint, tgtZone, theZone.impactRadius, theZone.name, theZone.num)
return gData
end
function launchPlatform.launchAtTargetZone(coa, tgtZone, theType) -- gets closest platform for target
-- type currently not supported
local platforms = launchPlatform.redLaunchers
if coa == 2 then platforms = launchPlatform.blueLaunchers end
local cty = dcsCommon.getACountryForCoalition(coa)
-- get closest launcher for target
local tgtPoint = tgtZone:getPoint()
local src, dist = cfxZones.getClosestZone(tgtPoint, platforms)
trigger.action.outText("+++LP: chosen <" .. src.name .. "> as launch platform", 30)
local theLauncher = launchPlatform.launchForPlatform(coa, src, tgtPoint, tgtZone)
if not theLauncher then
trigger.action.outText("NO LAUNCHER", 30)
return nil
end
-- if type is tomahawk, the platform is ship = 3
local theGroup = coalition.addGroup(cty, 3, theLauncher)
if not theGroup then
trigger.action.outText("!!!!!!!!!!!!!NOPE", 30)
return
end
-- we remove the group in some time
local now = timer.getTime()
timer.scheduleFunction(launchPlatform.asynchRemovePlatform, theGroup:getName(), now + 300)
end
function launchPlatform.asynchRemovePlatform(args)
trigger.action.outText("LP: asynch remove for group <" .. args .. ">", 30)
local theGroup = Group.getByName(args)
if not theGroup then return end
Group.destroy(theGroup)
end
function launchPlatform.createData(thePoint, theTarget, targetZone, radius, name, num, wType)
-- if present, we can use targetZone with some intelligence
if not thePoint then
trigger.action.outText("NO POINT", 30)
return nil
end
if not theTarget then
trigger.action.outText("NO TARGET", 30)
return nil
end
if not wType then wType = 52613349374 end
if not radius then radius = 1000 end
local useQty = true
if not num then num = 15 end
if num > 30 then num = 30 end -- max 30 missiles
if not name then name = "launcherDML" end
local gData = {
["visible"] = false,
["tasks"] = {},
["uncontrollable"] = false,
["route"] = {
["points"] = {
[1] = {
["alt"] = 0,
["type"] = "Turning Point",
["ETA"] = 0,
["alt_type"] = "BARO",
["formation_template"] = "",
["y"] = thePoint.z,
["x"] = thePoint.x,
["ETA_locked"] = true,
["speed"] = 0,
["action"] = "Turning Point",
["task"] = {
["id"] = "ComboTask",
["params"] = {
["tasks"] = {
[1] = {
["number"] = 1,
["auto"] = false,
["id"] = "FireAtPoint",
["enabled"] = true,
["params"] = {
["y"] = theTarget.z,
["x"] = theTarget.x,
["expendQtyEnabled"] = true,
["alt_type"] = 1,
["templateId"] = "",
["expendQty"] = 2,
["weaponType"] = wType,
["zoneRadius"] = radius,
}, -- end of ["params"]
}, -- end of [1]
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["speed_locked"] = true,
}, -- end of [1]
}, -- end of ["points"]
}, -- end of ["route"]
["hidden"] = false,
["units"] = {
[1] = {
["modulation"] = 0,
["skill"] = "Average",
["type"] = "USS_Arleigh_Burke_IIa",
["y"] = thePoint.z,
["x"] = thePoint.x,
["name"] = dcsCommon.uuid(name),
["heading"] = 2.2925180610373,
["frequency"] = 127500000,
}, -- end of [1]
}, -- end of ["units"]
["y"] = thePoint.z,
["x"] = thePoint.x,
["name"] = dcsCommon.uuid(name),
["start_time"] = 0,
}
-- now create the tasks block replacements
-- create random target locations inside
-- target point with radius and launch 2 per salvo
-- perhaps add some inteligence to target resource points
-- if inside camp
local hiPrioTargets
if targetZone and targetZone.cloners and #targetZone.cloners > 0 then
trigger.action.outText("+++LP: detected <" .. targetZone.name .. "> is camp with <" .. #targetZone.cloners .. "> res-points, re-targeting hi-prio", 30)
hiPrioTargets = targetZone.cloners
radius = radius / 10 -- much smaller error
end
local tasks = {}
for i=1, num do
local dp = dcsCommon.randomPointInCircle(radius, 0)
if hiPrioTargets then
-- choose one of the
local thisCloner = dcsCommon.pickRandom(hiPrioTargets)
local tp = thisCloner:getPoint()
dp.x = dp.x + tp.x
dp.z = dp.z + tp.z
else
dp.x = dp.x + theTarget.x
dp.z = dp.z + theTarget.z
end
local telem = {
["number"] = i,
["auto"] = false,
["id"] = "FireAtPoint",
["enabled"] = true,
["params"] = {
["y"] = dp.z,
["x"] = dp.x,
["expendQtyEnabled"] = true,
["alt_type"] = 1,
["templateId"] = "",
["expendQty"] = 1,
["weaponType"] = wType,
["zoneRadius"] = radius,
}, -- end of ["params"]
} -- end of [1]
-- table.insert(tasks, telem)
tasks[i] = telem
end
-- now replace old task with new
gData.route.points[1].task.params.tasks = tasks
return gData
end
--
-- start up
--
function launchPlatform.readConfigZone()
local theZone = cfxZones.getZoneByName("launchPlatformConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("launchPlatformConfig")
end
launchPlatform.verbose = theZone.verbose
end
function launchPlatform.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx launchPlatform requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx launchPlatform", launchPlatform.requiredLibs) then
return false
end
-- read config
launchPlatform.readConfigZone()
-- process launchPlatform Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("launchPlatform")
for k, aZone in pairs(attrZones) do
launchPlatform.readLaunchPlatform(aZone) -- process attributes
launchPlatform.addLaunchPlatform(aZone) -- add to list
end
-- say hi
trigger.action.outText("launchPlatform v" .. launchPlatform.version .. " started.", 30)
return true
end
if not launchPlatform.start() then
trigger.action.outText("launchPlatform failed to start.", 30)
launchPlatform = nil
end

View File

@ -678,7 +678,9 @@ end
function milHelo.GCcollected(gName)
-- do some housekeeping?
if milHelo.verbose then
trigger.action.outText("removed flight <" .. gName .. ">", 30)
end
end
function milHelo.GC()

View File

@ -1,5 +1,5 @@
noGap = {}
noGap.version = "1.0.0"
noGap.version = "1.0.1"
noGap.verbose = false
noGap.ignoreMe = "-ng" -- ignore altogether
@ -37,6 +37,7 @@ noGap.requiredLibs = {
Version History
1.0.0 - Initial version
1.0.1 - added "from runway"
--]]--
@ -80,6 +81,7 @@ function noGap.isGroundStart(theGroup)
if action == "Fly Over Point" then return false end
if action == "Turning Point" then return false end
if action == "Landing" then return false end
if action == "From Runway" then return false end
-- aircraft is on the ground - but is it in water (carrier)?
local u1 = theGroup.units[1]
local sType = land.getSurfaceType(u1) -- has fields x and y

74
modules/slotty.lua Normal file
View File

@ -0,0 +1,74 @@
slotty = {}
slotty.version = "1.1.0"
--[[--
Single-player slot blocking and slot blocking fallback
for multiplayer when SSB is not installed on server.
Uses SSB's method of marking groups with flags
(c) 2024 by Christian Franz
Slotty can be disabled by setting the value of the flag named "noSlotty" to
a value greater than zero
Version history
1.0.0 - Initial version
1.1.0 - "noSlotty" global disable flag, anti-mirror SSB flag
--]]--
function slotty:onEvent(event)
if not event.initiator then return end
local theUnit = event.initiator
if not theUnit.getPlayerName then return end
local pName = theUnit:getPlayerName()
if not pName then return end
local uName = theUnit:getName()
local theGroup = theUnit:getGroup()
local gName = theGroup:getName()
if event.id == 15 then -- birth
if trigger.misc.getUserFlag("noSlotty") > 0 then return end
local np = net.get_player_list() -- retruns a list of PID
local isSP = false
if not np or (#np < 1) then
isSP = true -- we are in single-player mode
end
-- now see if that group name is currently blocked
local blockstate = false
if trigger.misc.getUserFlag(gName) > 0 then
trigger.action.outText("Group <" .. gName .. "> is currently blocked and can't be entered", 30)
blockstate = true
end
if not blockstate then return end -- nothing left to do, all is fine
-- interface with SSBClient for compatibility
if cfxSSBClient and cfxSSBClient.occupiedUnits then
cfxSSBClient.occupiedUnits[uName] = nil
end
if isSP then
theUnit:destroy() -- SP kill, works only in Single-player
return
end
-- we would leave the rest to SSB, but if we get here, SSB is
-- not installed on host, so we proceed with invoking netAPI
for idx,pid in pairs(np) do
local netName = net.get_name(pid)
if netName == pName then
timer.scheduleFunction(slotty.kick, pid, timer.getTime() + 0.1)
return
end
end
end
end
function slotty.kick(pid)
net.force_player_slot(pid, 0, '') -- '', thanks Dz!
end
function slotty.start()
world.addEventHandler(slotty)
trigger.action.outText("slotty v " .. slotty.version .. " running.", 30)
end
slotty.start()

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {}
cfxSpawnZones.version = "2.0.1"
cfxSpawnZones.version = "2.0.2"
cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
@ -27,6 +27,8 @@ cfxSpawnZones.spawnedGroups = {}
- baseName defaults to zone name, as it is safe for naming
- spawnWithSpawner direct link in spawner to spawnZones
2.0.1 - fix in verifySpawnOwnership() when not master zone found
2.0.2 - new "moveFormation" attribute
--]]--
cfxSpawnZones.allSpawners = {}
@ -128,6 +130,13 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.count = 1 -- used to create names, and count how many groups created
theSpawner.theSpawn = nil -- link to last spawned group
theSpawner.formation = inZone:getStringFromZoneProperty("formation", "circle_out")
theSpawner.moveFormation = inZone:getStringFromZoneProperty("moveFormation", "Custom")
if theSpawner.moveFormation == "Custom" or theSpawner.moveFormation == "EchelonR" or theSpawner.moveFormation == "EchelonL" or theSpawner.moveFormation == "Diamond" or theSpawner.moveFormation == "Vee" or theSpawner.moveFormation == "Cone" or theSpawner.moveFormation == "Rank" then -- all fine, do nothing
else
trigger.action.outText("+++SpwZ: unknown moveFormation <" .. theSpawner.moveFormation .. "> in spawn zone <" .. inZone.name .. ">, defaulting to 'Custom'", 30)
theSpawner.moveFormation = "Custom"
end
theSpawner.paused = inZone:getBoolFromZoneProperty("paused", false)
-- orders are always converted to all lower case
theSpawner.orders = inZone:getStringFromZoneProperty("orders", "guard"):lower()
@ -297,6 +306,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
troopData.groupData = theData
troopData.orders = aSpawner.orders -- always set
troopData.side = theCoalition
troopData.moveFormation = aSpawner.moveFormation
troopData.target = aSpawner.target -- can be nil!
troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil
troopData.range = aSpawner.range
@ -318,7 +328,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
AI.Option.Ground.val.ROE.WEAPON_HOLD,
1.0)
else
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders)
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders, aSpawner.moveFormation)
cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- see if we have defined a target zone as destination
@ -576,6 +586,7 @@ function cfxSpawnZones.loadData()
for gName, gdTroop in pairs (allTroopData) do
local gData = gdTroop.groupData
local orders = gdTroop.orders
local moveFormation = gdTroop.moveFormation
local target = gdTroop.target
local tracker = gdTroop.tracker
local side = gdTroop.side
@ -598,7 +609,7 @@ function cfxSpawnZones.loadData()
1.0)
else
-- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation)
cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- engage a target zone
if target then

View File

@ -1,6 +1,6 @@
-- theDebugger 2.x
debugger = {}
debugger.version = "2.1.0"
debugger.version = "2.1.1"
debugDemon = {}
debugDemon.version = "2.1.0"
@ -39,6 +39,7 @@ debugger.log = ""
debug invocation on clone of data structure
readback verification of flag set
fixed getProperty() in debugger with zone
2.1.1 - removed bug that skipped events? when zone not verbose
--]]--
@ -267,13 +268,15 @@ function debugger.createEventMonWithZone(theZone)
end
for idx, aFlag in pairs(flagArray) do
local evt = tonumber(aFlag)
if evt and (debugger.verbose or theZone.verbose) then
if evt then
if evt < 0 then evt = 0 end
if evt > 57 then evt = 57 end
debugger.showEvents[evt] = debugDemon.eventList[tostring(evt)]
if (debugger.verbose or theZone.verbose) then
debugger.outText(" monitoring event <" .. debugger.showEvents[evt] .. ">", 30)
end
end
end
end
--

View File

@ -1,5 +1,5 @@
unitPersistence = {}
unitPersistence.version = '2.0.0'
unitPersistence.version = '2.0.1'
unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = {
@ -21,6 +21,7 @@ unitPersistence.requiredLibs = {
- fixed air spawn (fixed wing)
2.0.0 - dmlZones, OOP
cleanup
2.0.1 - cosmetic verbosity during save
REQUIRES PERSISTENCE AND MX
@ -162,7 +163,9 @@ function unitPersistence.saveData()
end
else
theUnitData.isDead = true
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence - unit <" .. uName .. "> of group <" .. groupName .. "> is dead or non-existant", 30)
end
end -- is alive and exists?
end -- unit maybe not dead
end -- iterate units in group