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

View File

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

View File

@ -1,5 +1,5 @@
bombRange = {} bombRange = {}
bombRange.version = "1.1.2" bombRange.version = "1.1.3"
bombRange.dh = 1 -- meters above ground level burst bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = { bombRange.requiredLibs = {
@ -21,6 +21,7 @@ VERSION HISTORY
1.1.1 - fixed reading smoke color for zone 1.1.1 - fixed reading smoke color for zone
minor clean-up minor clean-up
1.1.2 - corrected bug when no bomb range is detected 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 bombRange.bombs = {} -- live tracking
@ -543,7 +544,8 @@ function bombRange.impacted(weapon, target, finalPass)
tDist = math.floor(tDist*100) /100 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) 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 end
local meters = math.floor(minDist * 10) / 10
local feet = math.floor(minDist * 3.28084 * 10) / 10
local msg = "" local msg = ""
if impactInside then if impactInside then
local percentage = 0 local percentage = 0
@ -553,15 +555,17 @@ function bombRange.impacted(weapon, target, finalPass)
percentage = 1 - (minDist / theRange.radius) percentage = 1 - (minDist / theRange.radius)
percentage = math.floor(percentage * 100) percentage = math.floor(percentage * 100)
end end
msg = "INSIDE target area" msg = "INSIDE target area"
if theRange.reportName then msg = msg .. " " .. theRange.name end 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 targetName then msg = msg .. ", hit on " .. targetName end
if not theRange.usePercentage then if not theRange.usePercentage then
percentage = 100 percentage = 100
else else
msg = msg .. " (Quality " .. percentage .."%)" msg = msg .. " (Quality " .. percentage .."%)" --, off-center by " .. meters .. "m/" .. feet .. "ft)"
end end
if theRange.hitOut then if theRange.hitOut then
@ -572,7 +576,7 @@ function bombRange.impacted(weapon, target, finalPass)
else else
msg = "Outside target area" msg = "Outside target area"
if theRange.reportName then msg = msg .. " " .. theRange.name end 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." msg = msg .. ", no hit."
bombRange.addImpactForWeapon(weapon, false, 0) bombRange.addImpactForWeapon(weapon, false, 0)
end end

View File

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

View File

@ -1075,7 +1075,7 @@ function csarManager.updateCSARMissions()
else else
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR." local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
trigger.action.outTextForCoalition(aMission.side, msg, 30) 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) csarManager.invokeCallbacks(aMission.side, false, 1, "KIA", aMission)
end end
end end
@ -1765,4 +1765,6 @@ end
-- may want to change if time limit was exceeded on return to tell -- may want to change if time limit was exceeded on return to tell
player that they did not survive the transport 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 = {}
dcsCommon.version = "3.0.6" dcsCommon.version = "3.0.7"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option

View File

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

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "3.0.3" cfxHeloTroops.version = "3.0.4"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -40,6 +40,7 @@ cfxHeloTroops.requestRange = 500 -- meters
3.0.1 - fixed a bug with legalTroops attribute 3.0.1 - fixed a bug with legalTroops attribute
3.0.2 - fixed a typo in in-air menu 3.0.2 - fixed a typo in in-air menu
3.0.3 - pointInZone check for insertion rather than radius 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 = { cfxHeloTroops.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "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 "cfxZones", -- Zones, of course
"cfxCommander", -- to make troops do stuff "cfxCommander", -- to make troops do stuff
"cfxGroundTroops", -- generic when dropping troops "cfxGroundTroops", -- generic when dropping troops
@ -641,6 +640,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local orders = conf.troopsOnBoard.orders local orders = conf.troopsOnBoard.orders
local dest = conf.troopsOnBoard.destination local dest = conf.troopsOnBoard.destination
local theName = conf.troopsOnBoard.name local theName = conf.troopsOnBoard.name
local moveFormation = conf.troopsOnBoard.moveFormation
if not orders then orders = "guard" end if not orders then orders = "guard" end
@ -670,7 +670,15 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
troopData.destination = dest -- only for attackzone orders troopData.destination = dest -- only for attackzone orders
cfxHeloTroops.deployedTroops[theData.name] = troopData 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 troop.destination = dest -- transfer target zone for attackzone oders
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30) 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.orders = pooledGroup.orders
conf.troopsOnBoard.range = pooledGroup.range conf.troopsOnBoard.range = pooledGroup.range
conf.troopsOnBoard.destination = pooledGroup.destination -- may be nil 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) cfxGroundTroops.removeTroopsFromPool(pooledGroup)
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30) 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") --trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav")
else else
if cfxHeloTroops.verbose then if cfxHeloTroops.verbose then

View File

@ -65,10 +65,12 @@ function income.update()
has, balance = bank.getBalance(1) has, balance = bank.getBalance(1)
tick = string.gsub(income.tickMessage, "<i>", redI) tick = string.gsub(income.tickMessage, "<i>", redI)
trigger.action.outTextForCoalition(1, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30) trigger.action.outTextForCoalition(1, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
trigger.action.outSoundForCoalition(1, income.reportSound)
has, balance = bank.getBalance(2) has, balance = bank.getBalance(2)
tick = string.gsub(income.tickMessage, "<i>", blueI) tick = string.gsub(income.tickMessage, "<i>", blueI)
trigger.action.outTextForCoalition(2, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30) trigger.action.outTextForCoalition(2, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
trigger.action.outSoundForCoalition(2, income.reportSound)
end end
end end
@ -88,6 +90,8 @@ function income.readConfigZone()
income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes
income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available: §<i>") income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available: §<i>")
income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true) income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true)
income.reportSound = theZone:getStringFromZoneProperty("reportSound", "<none>")
income.verbose = theZone.verbose income.verbose = theZone.verbose
end 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) function milHelo.GCcollected(gName)
-- do some housekeeping? -- do some housekeeping?
trigger.action.outText("removed flight <" .. gName .. ">", 30) if milHelo.verbose then
trigger.action.outText("removed flight <" .. gName .. ">", 30)
end
end end
function milHelo.GC() function milHelo.GC()

View File

@ -1,5 +1,5 @@
noGap = {} noGap = {}
noGap.version = "1.0.0" noGap.version = "1.0.1"
noGap.verbose = false noGap.verbose = false
noGap.ignoreMe = "-ng" -- ignore altogether noGap.ignoreMe = "-ng" -- ignore altogether
@ -37,6 +37,7 @@ noGap.requiredLibs = {
Version History Version History
1.0.0 - Initial version 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 == "Fly Over Point" then return false end
if action == "Turning Point" then return false end if action == "Turning Point" then return false end
if action == "Landing" 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)? -- aircraft is on the ground - but is it in water (carrier)?
local u1 = theGroup.units[1] local u1 = theGroup.units[1]
local sType = land.getSurfaceType(u1) -- has fields x and y 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 = {}
cfxSpawnZones.version = "2.0.1" cfxSpawnZones.version = "2.0.2"
cfxSpawnZones.requiredLibs = { cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- 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 - baseName defaults to zone name, as it is safe for naming
- spawnWithSpawner direct link in spawner to spawnZones - spawnWithSpawner direct link in spawner to spawnZones
2.0.1 - fix in verifySpawnOwnership() when not master zone found 2.0.1 - fix in verifySpawnOwnership() when not master zone found
2.0.2 - new "moveFormation" attribute
--]]-- --]]--
cfxSpawnZones.allSpawners = {} cfxSpawnZones.allSpawners = {}
@ -128,6 +130,13 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.count = 1 -- used to create names, and count how many groups created theSpawner.count = 1 -- used to create names, and count how many groups created
theSpawner.theSpawn = nil -- link to last spawned group theSpawner.theSpawn = nil -- link to last spawned group
theSpawner.formation = inZone:getStringFromZoneProperty("formation", "circle_out") 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) theSpawner.paused = inZone:getBoolFromZoneProperty("paused", false)
-- orders are always converted to all lower case -- orders are always converted to all lower case
theSpawner.orders = inZone:getStringFromZoneProperty("orders", "guard"):lower() theSpawner.orders = inZone:getStringFromZoneProperty("orders", "guard"):lower()
@ -297,6 +306,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
troopData.groupData = theData troopData.groupData = theData
troopData.orders = aSpawner.orders -- always set troopData.orders = aSpawner.orders -- always set
troopData.side = theCoalition troopData.side = theCoalition
troopData.moveFormation = aSpawner.moveFormation
troopData.target = aSpawner.target -- can be nil! troopData.target = aSpawner.target -- can be nil!
troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil
troopData.range = aSpawner.range troopData.range = aSpawner.range
@ -318,7 +328,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
AI.Option.Ground.val.ROE.WEAPON_HOLD, AI.Option.Ground.val.ROE.WEAPON_HOLD,
1.0) 1.0)
else else
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders) local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders, aSpawner.moveFormation)
cfxGroundTroops.addGroundTroopsToPool(newTroops) cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- see if we have defined a target zone as destination -- see if we have defined a target zone as destination
@ -576,6 +586,7 @@ function cfxSpawnZones.loadData()
for gName, gdTroop in pairs (allTroopData) do for gName, gdTroop in pairs (allTroopData) do
local gData = gdTroop.groupData local gData = gdTroop.groupData
local orders = gdTroop.orders local orders = gdTroop.orders
local moveFormation = gdTroop.moveFormation
local target = gdTroop.target local target = gdTroop.target
local tracker = gdTroop.tracker local tracker = gdTroop.tracker
local side = gdTroop.side local side = gdTroop.side
@ -598,7 +609,7 @@ function cfxSpawnZones.loadData()
1.0) 1.0)
else else
-- add to groundTroops -- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders) local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation)
cfxGroundTroops.addGroundTroopsToPool(newTroops) cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- engage a target zone -- engage a target zone
if target then if target then

View File

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

View File

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