Version 2.2.1

csarManager enhancements etc.
This commit is contained in:
Christian Franz 2024-04-04 13:25:33 +02:00
parent 3111d2e804
commit 8f7371825d
15 changed files with 848 additions and 331 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.3.0"
cfxZones.version = "4.3.1"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -46,6 +46,8 @@ cfxZones.version = "4.3.0"
- 4.1.2 - hash property missing warning
- 4.2.0 - new createRandomPointInPopulatedZone()
- 4.3.0 - boolean supports maybe, random, rnd, ?
- small optimization for randomInRange()
- randomDelayFromPositiveRange also allows 0
--]]--
@ -977,7 +979,7 @@ function cfxZones.isGroupPartiallyInZone(aGroup, aZone)
if not aGroup:isExist() then return false end
local allUnits = aGroup:getUnits()
for uk, aUnit in pairs (allUnits) do
if aUnit:isExist() and aUnit:getLife() > 1 then
if Unit.isExist(aUnit) and aUnit:getLife() > 1 then
local p = aUnit:getPoint()
local inzone, percent, dist = cfxZones.pointInZone(p, aZone)
if inzone then
@ -2244,8 +2246,9 @@ end
function cfxZones.randomDelayFromPositiveRange(minVal, maxVal) -- should be moved to dcsCommon
if not maxVal then return minVal end
if not minVal then return maxVal end
if minVal == maxVal then return minVal end
local delay = maxVal
if minVal > 0 and minVal < delay then
if minVal >= 0 and minVal < delay then
-- we want a randomized from time from minTime .. delay
local varPart = delay - minVal + 1
varPart = dcsCommon.smallRandom(varPart) - 1

199
modules/clients.lua Normal file
View File

@ -0,0 +1,199 @@
clients = {}
clients.version = "0.0.0"
clients.ups = 1
clients.verbose = false
clients.netlog = true
clients.players = {}
-- player entry: indexed by name
-- playerName - name of player, same as index
-- uName = unit name
-- coa = coalition
-- connected = true/false is currently ingame
function clients.out(msg)
-- do some preprocessing?
if clients.verbose then
trigger.action.outText(msg, 30)
end
-- add to own log?
if clients.netlog then
env.info(msg)
end
end
--[[--
Event ID:
1 = player enters mission for first time
2 = player enters unit
3 = player leaves unit
4 = player changes unit
5 = player changes coalition
Sequence of events
Player enters mission first time
- player enters mission (new player) ID = 1
- player enters unit ID = 2
Player is no longer active (their unit is gone)
- player leaves unit ID = 3
Player enters unit after having already been in the mission
- (player changes coalition) if unit belongs to different coa
- (player changes unit if unit different than before)
- player enters unit
--]]--
--
-- client events
--
clients.cb = {} -- profile = (id, this, last)
function clients.invokeCallbacks(ID, this, last)
for idx, cb in pairs(clients.cb) do
cb(ID, this, last)
end
end
function clients.addCallback(theCB)
table.insert(clients.cb, theCB)
end
function clients.playerEnteredMission(thisTime)
clients.out("clients: Player <" .. thisTime.playerName .. "> enters mission for the first time")
clients.invokeCallbacks(1, thisTime)
end
function clients.playerEnteredUnit(thisTime)
-- called when player enters a unit
clients.out("clients: Player <" .. thisTime.playerName .. "> enters Unit <" .. thisTime.uName .. ">.")
clients.invokeCallbacks(2, thisTime)
end
function clients.playerLeavesUnit(lastTime)
-- called when player leaves a unit
clients.out("clients: Player <" .. lastTime.playerName .. "> leaves Unit <" .. lastTime.uName .. ">.")
clients.invokeCallbacks(3, lastTime)
end
function clients.playerChangedUnits(thisTime, lastTime)
-- called when player enters a different unit
clients.out("clients: Player <" .. thisTime.playerName .. "> changes from Unit <" .. lastTime.uName .. "> to NEW unit <" .. thisTime.uName .. ">.")
clients.invokeCallbacks(4, thisTime, lastTime)
end
function clients.playerChangedCoalition(thisTime, lastTime)
-- called when player enters a different unit
clients.out("clients: Player <" .. thisTime.playerName .. "> changes from coalition <" .. lastTime.coa .. "> to NEW coalition <" .. thisTime.coa .. ">.")
clients.invokeCallbacks(4, thisTime, lastTime)
end
-- check all connected player units
function clients.compareStatus(thisTime, lastTime)
if lastTime then
-- they were known last time I checked. see if they were in-game
if thisTime.connected == lastTime.connected then
-- status is the same as before
else
-- player entered or left mission, and was known last time
if thisTime.connected then
-- player connected but was known, do nothing
else
-- player left mission. do we want to record this?
end
end
-- check if they have the same unit name
-- if not, check if they have changed coas
if lastTime.uName == thisTime.uName then
-- same unit, all is fine
else
-- new unit. check if same side
if lastTime.coa == thisTime.coa then
-- player stayed in same coa
else
-- player changed coalition
clients.playerChangedCoalition(thisTime, lastTime)
end
clients.playerEnteredUnit(thisTime)
clients.playerChangedUnits(thisTime, lastTime)
end
else
-- player is new to mission
clients.playerEnteredMission(thisTime)
clients.playerEnteredUnit(thisTime)
end
end
function clients.checkPlayers()
local connectedNow = {} -- players that are connected now
local allCoas = {0, 1, 2}
-- collect all currently connected players
for idx, coa in pairs(allCoas) do
local cPlayers = coalition.getPlayers(coa) -- gets UNITS!
for idy, aPlayerUnit in pairs(cPlayers) do
if aPlayerUnit and Unit.isExist(aPlayerUnit) then
local entry = {}
local playerName = aPlayerUnit:getPlayerName()
entry.playerName = playerName
entry.uName = aPlayerUnit:getName()
entry.coa = coa
entry.connected = true
connectedNow[playerName] = entry
-- see if they were connected last time we checked
local lastTime = clients.players[playerName]
clients.compareStatus(entry, lastTime)
end
end
end
-- now find players who are no longer represented and
-- event them
for aPlayerName, lastTime in pairs(clients.players) do
local thisTime = connectedNow[aPlayerName]
if thisTime then
-- is also present now. skip
else
-- no longer active, see if they were active last time
if lastTime.connected then
-- they were active, generate disco event
clients.playerLeavesUnit(lastTime)
end
lastTime.connected = false
-- keep on roster
connectedNow[aPlayerName] = lastTime
end
end
clients.players = connectedNow
end
function clients.update()
timer.scheduleFunction(clients.update, {}, timer.getTime() + 1)
clients.checkPlayers()
end
--
-- Event handling
--
function clients:onEvent(theEvent)
if not theEvent then return end
local theUnit = theEvent.initiator
if not theUnit then return end
if not theUnit.getPlayerName or not theUnit:getPlayerName() then return end
-- we have a player birth. Simply invoke checkplayers
clients.out("clients: detected player birth event.")
clients.checkPlayers()
end
--
-- Start
--
function clients.start()
world.addEventHandler(clients)
timer.scheduleFunction(clients.update, {}, timer.getTime() + 1)
trigger.action.outText("clients v" .. clients.version .. " running.", 30)
end
clients.start()

View File

@ -98,8 +98,6 @@ function cloneZones.partOfGroupDataInZone(theZone, theUnits)
uP.x = aUnit.x
uP.y = 0
uP.z = aUnit.y -- !! y-z
--local dist = dcsCommon.dist(uP, zP)
--if dist <= theZone.radius then return true end
if theZone:pointInZone(uP) then return true end
end
return false
@ -107,7 +105,6 @@ end
function cloneZones.allGroupsInZoneByData(theZone)
local theGroupsInZone = {}
local radius = theZone.radius
for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then
if cloneZones.partOfGroupDataInZone(theZone, groupData.units) then

View File

@ -4,7 +4,7 @@
-- *** EXTENDS ZONES: 'pathing' attribute
--
cfxCommander = {}
cfxCommander.version = "1.1.3"
cfxCommander.version = "1.1.4"
--[[-- VERSION HISTORY
- 1.0.5 - createWPListForGroupToPointViaRoads: detect no road found
- 1.0.6 - build in more group checks in assign wp list
@ -29,6 +29,7 @@ cfxCommander.version = "1.1.3"
- added delay defaulting for most scheduling functions
- 1.1.3 - isExist() guard improvements for multiple methods
- cleaned up comments
- 1.1.4 - hardened makeGroupGoThere()
--]]--
@ -337,6 +338,18 @@ function cfxCommander.makeGroupGoThere(group, there, speed, formation, delay)
if type(group) == 'string' then -- group name
group = Group.getByName(group)
end
if not Group.isExist(group) then
trigger.action.outText("cmdr: makeGroupGoThere() - group does not exist", 30)
return
end
-- check that we can get a location for the group
local here = dcsCommon.getGroupLocation(group)
if not here then
return
end
local wp = cfxCommander.createWPListForGroupToPoint(group, there, speed, formation)
cfxCommander.assignWPListToGroup(group, wp, delay)

View File

@ -40,6 +40,8 @@ csarManager.ups = 1
3.2.4 - pass theZone with missionCreateCB when created from zone
3.2.5 - smoke callbacks
- useRanks option
3.2.6 - inBuiltup analogon to cloner
INTEGRATES AUTOMATICALLY WITH playerScore
@ -1277,7 +1279,7 @@ function csarManager.createCSARMissionFromZone(theZone)
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
elseif theZone.inPopulated then
local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance, theZone.maxTries)
local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance) -- no more maxTries: theZone.maxTries)
mPoint = aPoint -- safety in case we need to mod aPoint
end
local theMission = csarManager.createCSARMissionData(
@ -1409,12 +1411,28 @@ function csarManager.readCSARZone(theZone)
theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true)
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
theZone.inPopulated = theZone:getBoolFromZoneProperty("inPopulated", false)
theZone.clearance = theZone:getNumberFromZoneProperty("clearance", 10)
theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20)
if theZone:hasProperty("onRoads") then
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoads", false)
end
-- emulate inBuiltup from clone Zone
if theZone:hasProperty("inPopulated") or theZone:hasProperty("clearance") or theZone:hasProperty("inBuiltup")then
if theZone:hasProperty("inPopulated") then
theZone.inPopulated = theZone:getBoolFromZoneProperty("inPopulated", false)
else
theZone.inPopulated = true -- presence of clearance forces it to true
end
theZone.clearance = theZone:getNumberFromZoneProperty("clearance", 10)
if theZone:hasProperty("inBuiltUp") then
theZone.clearance = theZone:getNumberFromZoneProperty("inBuiltup", 10)
end
end
-- maxTries is decommed
-- theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20)
if theZone.onRoad and theZone.inPopulated then
trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30)
theZone.inPopulated = false
end
-- add to list of startable csar
@ -1423,27 +1441,6 @@ function csarManager.readCSARZone(theZone)
end
if (not deferred) then
--[[--
local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
elseif theZone.inPopulated then
local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance, theZone.maxTries)
mPoint = aPoint -- safety in case we need to mod aPoint
end
local theMission = csarManager.createCSARMissionData(
mPoint,
theZone.csarSide,
theZone.csarFreq,
theZone.csarName,
theZone.numCrew,
theZone.timeLimit,
theZone.csarMapMarker,
0.1, -- theZone.radius,
nil) -- parashoo unit
csarManager.addMission(theMission, theZone)
--]]--
local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission, theZone)
end

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.0.3"
dcsCommon.version = "3.0.5"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -13,6 +13,10 @@ dcsCommon.version = "3.0.3"
3.0.3 - createStaticObjectForCoalitionInRandomRing() returns x and z
- isTroopCarrier() also supports 'helo' keyword
- new createTakeOffFromGroundRoutePointData()
3.0.4 - getGroupLocation() hardened, optional verbose
3.0.5 - new getNthItem()
- new getFirstItem()
--]]--
-- dcsCommon is a library of common lua functions
@ -184,6 +188,18 @@ dcsCommon.version = "3.0.3"
return choices[rtnVal] -- return indexed
end
function dcsCommon.getNthItem(theSet, n)
local count = 1
for key, value in pairs(theSet) do
if count == n then return value end
count = count + 1
end
return nil
end
function dcsCommon.getFirstItem(theSet)
return dcsCommon.getNthItem(theSet, 1)
end
function dcsCommon.getSizeOfTable(theTable)
local count = 0
@ -888,7 +904,8 @@ dcsCommon.version = "3.0.3"
-- get group location: get the group's location by
-- accessing the fist existing, alive member of the group that it finds
function dcsCommon.getGroupLocation(group)
function dcsCommon.getGroupLocation(group, verbose, gName)
if not verbose then verbose = false end
-- nifty trick from mist: make this work with group and group name
if type(group) == 'string' then -- group name
group = Group.getByName(group)
@ -896,12 +913,18 @@ dcsCommon.version = "3.0.3"
-- get all units
local allUnits = group:getUnits()
if not allUnits then
if verbose then
trigger.action.outText("++++common: no group location for <" .. gName .. ">, skipping.", 30)
end
return nil
end
-- iterate through all members of group until one is alive and exists
for index, theUnit in pairs(allUnits) do
if (theUnit:isExist() and theUnit:getLife() > 0) then
return theUnit:getPosition().p
end;
end
end
-- if we get here, there was no live unit

View File

@ -1,5 +1,5 @@
groundExplosion = {}
groundExplosion.version = "1.0.0"
groundExplosion.version = "1.1.0"
groundExplosion.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -9,6 +9,8 @@ groundExplosion.zones = {}
--[[--
Version History
1.0.0 - Initial version
1.0.1 - fixed lib check for objectDestructDetector
1.1.0 - new flares attribute
--]]--
@ -28,6 +30,9 @@ function groundExplosion.addExplosion(theZone)
end
theZone.duration = theZone:getNumberFromZoneProperty("duration", 0)
theZone.aglMin, theZone.aglMax = theZone:getPositiveRangeFromZoneProperty("AGL", 1,1)
if theZone:hasProperty("flares") then
theZone.flareMin, theZone.flareMax = theZone:getPositiveRangeFromZoneProperty("flares", 0-3)
end
end
--
@ -38,6 +43,16 @@ function groundExplosion.doBoom(args)
local power = args[2]
local theZone = args[3]
trigger.action.explosion(loc, power)
if theZone.flareMin then
local flareNum = cfxZones.randomInRange(theZone.flareMin, theZone.flareMax)
if flareNum > 0 then
for i=1, flareNum do
local azimuth = math.random(360)
azimuth = azimuth * 0.0174533 -- in rads
trigger.action.signalFlare(loc, 2, azimuth) -- 2 = white
end
end
end
end
function groundExplosion.startBoom(theZone)
@ -82,7 +97,7 @@ end
function groundExplosion.start()
if not dcsCommon.libCheck("cfx groundExplosion",
cfxObjectDestructDetector.requiredLibs) then
groundExplosion.requiredLibs) then
return false
end

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {}
cfxGroundTroops.version = "1.7.8"
cfxGroundTroops.version = "2.0.0"
cfxGroundTroops.ups = 1
cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = {
@ -22,52 +22,15 @@ cfxGroundTroops.requiredLibs = {
-- module
cfxGroundTroops.deployedTroops = {} -- indexed by group name
cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
--[[--
version history
1.3.0 - added "wait-" prefix to have toops do nothing
- added lazing
1.3.1 - sound for lazing msg is "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav"
- lazing --> lasing in text
1.3.2 - set ups to 2
1.4.0 - queued updates except for lazers
1.4.1 - makeTroopsEngageZone now issues hold before moving on 5 seconds later
- getTroopReport
- include size of group
1.4.2 - uses unitIsInfantry from dcsCommon
1.5.0 - new scheduled updates per troop to reduce processor load
- tiebreak code
1.5.1 - small bugfix in scheduled code
1.5.2 - checkSchedule
- speed warning in scheduler
- go off road when speed warning too much
1.5.3 - monitor troops
- managed queue for ground troops
- on second switch to offroad now removed from MQ
1.5.4 - removed debugging messages
1.5.5 - removed bug in troop report reading nil destination
1.6.0 - check modules
1.6.1 - troopsCallback management so you can be informed if a
troop you have added to the pool is dead or has achieved a goal.
callback will list reasons "dead" and "arrived"
updateAttackers
1.6.2 - also accept 'lase' as 'laze', translate directly
1.7.0 - now can use groundTroopsConfig zone
1.7.1 - addTroopsDeadCallback() renamed to addTroopsCallback()
- invokeCallbacksFor also accepts and passes on data block
- troops is always passed in data block as .troops
1.7.2 - callback when group is neutralized on guard orders
- callback when group is being engaged under guard orders
1.7.3 - callbacks for lase:tracking and lase:stop
1.7.4 - verbose flag, warnings suppressed
1.7.5 - some troop.group hardening with isExist()
1.7.6 - fixed switchToOffroad
1.7.7 - no longer case sensitive for orders
1.7.7 - updateAttackers() now inspects 'moving' status and invokes makeTroopsEngageZone
- makeTroopsEngageZone() sets 'moving' status to true
- createGroundTroops() sets moving status to false
- updateZoneAttackers() uses moving
1.7.8 - better guards before invoking ownedZones
2.0.0 - dmlZones
- jtacSound
- clanup
- jtacVerbose
an entry into the deployed troop table has the following attributes
- group - the group
@ -113,63 +76,47 @@ cfxGroundTroops.deployedTroops = {} -- indexed by group name
-- queued will work one every pass (except for lazed), distributing the load much better
-- schedueld installs a callback for each group separately and thus distributes the load over time much better
cfxGroundTroops.queuedUpdates = false -- set to true to process one group per turn. To work this way, scheduledUpdates must be false
cfxGroundTroops.scheduledUpdates = true -- set to false to allow queing of standard updates. overrides queuedUpdates
cfxGroundTroops.monitorNumbers = false -- set to true to debug managed group size
function cfxGroundTroops.invokeCallbacks(ID, jtac, tgt, data)
-- IS is aqui, lost, dead, jtac died. jtac is group, tgt is unit, data is rest
for idx, cb in pairs(cfxGroundTroops.jtacCB) do
cb(ID, jtac, tgt, data)
end
end
cfxGroundTroops.standardScheduleInterval = 30 -- 30 seconds between calls
cfxGroundTroops.guardUpdateInterval = 30 -- every 30 seconds we check up on guards
cfxGroundTroops.trackingUpdateInterval = 0.5 -- 0.5 seconds for lazer tracking etc
function cfxGroundTroops.addJtacCB(theCB)
table.insert(cfxGroundTroops.jtacCB, theCB)
end
cfxGroundTroops.maxManagedTroops = 67 -- -1 is infinite, any positive number turn on cap on managed troops and palces excess troops in queue
cfxGroundTroops.troopQueue = {} -- FIFO stack
-- return the best tracking interval for this type of orders
--
-- READ CONFIG ZONE TO OVERRIDE SETTING
--
function cfxGroundTroops.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("groundTroopsConfig")
if not theZone then
if cfxGroundTroops.verbose then
trigger.action.outText("***gndT: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("groundTroopsConfig")
end
-- ok, for each property, load it if it exists
if cfxZones.hasProperty(theZone, "queuedUpdates") then
cfxGroundTroops.queuedUpdates = cfxZones.getBoolFromZoneProperty(theZone, "queuedUpdates", false)
end
if cfxZones.hasProperty(theZone, "scheduledUpdates") then
cfxGroundTroops.scheduledUpdates = cfxZones.getBoolFromZoneProperty(theZone, "scheduledUpdates", false)
end
if cfxZones.hasProperty(theZone, "maxManagedTroops") then
cfxGroundTroops.maxManagedTroops = cfxZones.getNumberFromZoneProperty(theZone, "maxManagedTroops", 65)
end
if cfxZones.hasProperty(theZone, "monitorNumbers") then
cfxGroundTroops.monitorNumbers = cfxZones.getBoolFromZoneProperty(theZone, "monitorNumbers", false)
end
if cfxZones.hasProperty(theZone, "standardScheduleInterval") then
cfxGroundTroops.standardScheduleInterval = cfxZones.getNumberFromZoneProperty(theZone, "standardScheduleInterval", 30)
end
if cfxZones.hasProperty(theZone, "guardUpdateInterval") then
cfxGroundTroops.guardUpdateInterval = cfxZones.getNumberFromZoneProperty(theZone, "guardUpdateInterval", 30)
end
if cfxZones.hasProperty(theZone, "trackingUpdateInterval") then
cfxGroundTroops.trackingUpdateInterval = cfxZones.getNumberFromZoneProperty(theZone, "trackingUpdateInterval", 0.5)
end
if cfxZones.hasProperty(theZone, "verbose") then
cfxGroundTroops.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
end
cfxGroundTroops.queuedUpdates = theZone:getBoolFromZoneProperty("queuedUpdates", false)
cfxGroundTroops.scheduledUpdates = theZone:getBoolFromZoneProperty("scheduledUpdates", false)
cfxGroundTroops.maxManagedTroops = theZone:getNumberFromZoneProperty("maxManagedTroops", 67)
cfxGroundTroops.monitorNumbers = theZone:getBoolFromZoneProperty("monitorNumbers", false)
cfxGroundTroops.standardScheduleInterval = theZone:getNumberFromZoneProperty("standardScheduleInterval", 30)
cfxGroundTroops.guardUpdateInterval = theZone:getNumberFromZoneProperty("guardUpdateInterval", 30)
cfxGroundTroops.trackingUpdateInterval = theZone:getNumberFromZoneProperty("trackingUpdateInterval", 0.5)
cfxGroundTroops.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
cfxGroundTroops.jtacVerbose = theZone:getBoolFromZoneProperty("jtacVerbose", true)
cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("jtacLaserCode", 1688)
if theZone:hasProperty("lazeCode") then
cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("lazeCode", 1688)
end
if theZone:hasProperty("laseCode") then
cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("laseCode", 1688)
end
if theZone:hasProperty("laserCode") then
cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("laserCode", 1688)
end
cfxGroundTroops.verbose = theZone.verbose
if cfxGroundTroops.verbose then
trigger.action.outText("+++gndT: read config zone!", 30)
@ -355,8 +302,7 @@ function cfxGroundTroops.updateAttackers(troop)
troop.orders = "guard"
return
end
-- if we get here, we need no change
end
@ -437,7 +383,7 @@ function cfxGroundTroops.findLazeTarget(troop)
-- iterate through the list until we find the first target
-- that fits the bill and return it
-- trigger.action.outText("+++ looking at " .. #enemyGroups .. " laze groups", 30)
for i=1, #enemyGroups do
-- get all units for this group
local aGroup = enemyGroups[i].group -- remember, they are in a {dist, group} tuple
@ -448,12 +394,9 @@ function cfxGroundTroops.findLazeTarget(troop)
-- unit lives
-- now, we need to filter infantry. we do this by
-- pre-fetching the typeString
--troop.lazeTargetType = aUnit:getTypeName()
-- and checking if the name contains some infantry-
-- typical strings. Idea taken from JTAC script
local isInfantry = dcsCommon.unitIsInfantry(theUnit)
if not isInfantry then
-- this is a vehicle, is it in line of sight?
-- raise the point 2m above ground for both points
@ -466,16 +409,11 @@ function cfxGroundTroops.findLazeTarget(troop)
-- the nearest group to us in range
-- that is visible!
return aUnit
else
--trigger.action.outText("+++ ".. aUnit:getName() .."cant be seen", 30)
end -- if visible
else
-- trigger.action.outText("+++ ".. aUnit:getName() .." (".. troop.lazeTargetType .. ") is infantry", 30)
end -- if not infantry
end -- if infantry
end -- if alive
end -- for all units
end -- for all enemy groups
--trigger.action.outText("+++ find nearest laze target did not find anything to laze", 30)
return nil -- no unit found
end
@ -499,10 +437,12 @@ function cfxGroundTroops.trackLazer(troop)
if not troop.lazerPointer then
local there = troop.lazeTarget:getPoint()
troop.lazerPointer = Spot.createLaser(troop.lazingUnit,{x = 0, y = 2, z = 0}, there, 1688)
troop.lazerPointer = Spot.createLaser(troop.lazingUnit,{x = 0, y = 2, z = 0}, there, cfxGroundTroops.laseCode)
troop.lazeTargetType = troop.lazeTarget:getTypeName()
trigger.action.outTextForCoalition(troop.side, troop.name .. " tally target - lasing " .. troop.lazeTargetType .. "!", 30)
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if cfxGroundTroops.jtacVerbose then
trigger.action.outTextForCoalition(troop.side, troop.name .. " tally target - lasing " .. troop.lazeTargetType .. ", code " .. cfxGroundTroops.laseCode .. "!", 30)
trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
end
troop.lastLazerSpot = there -- remember last spot
local data = {}
data.enemy = troop.lazeTarget
@ -510,9 +450,7 @@ function cfxGroundTroops.trackLazer(troop)
cfxGroundTroops.invokeCallbacksFor("lase:tracking", troop, data)
return
end
-- if true then return end
-- if we get here, we update the lazerPointer
local there = troop.lazeTarget:getPoint()
-- we may only want to update the laser spot when dist > trigger
@ -531,17 +469,17 @@ function cfxGroundTroops.updateLaze(troop)
else
cfxGroundTroops.lazerOff(troop)
troop.lazeTarget = nil
trigger.action.outTextForCoalition(troop.side, troop.name .. " reports lasing " .. troop.lazeTargetType .. " interrupted. Re-acquiring.", 30)
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if cfxGroundTroops.jtacVerbose then
trigger.action.outTextForCoalition(troop.side, troop.name .. " reports lasing " .. troop.lazeTargetType .. " interrupted. Re-acquiring.", 30)
trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
end
troop.lazingUnit = nil
cfxGroundTroops.invokeCallbacksFor("lase:stop", troop)
return -- we'll re-acquire through a new unit next round
end
end
-- if we get here, a lazing unit
--local here = troop.lazingUnit:getPoint()
-- if we get here, a lazing unit
if troop.lazeTarget then
-- check if that target is alive and in range
if troop.lazeTarget:isExist() and troop.lazeTarget:getLife() >= 1 then
@ -551,8 +489,10 @@ function cfxGroundTroops.updateLaze(troop)
local there = troop.lazeTarget:getPoint()
if dcsCommon.dist(here, there) > troop.range then
-- troop out of range
trigger.action.outTextForCoalition(troop.side, troop.name .. " lost sight of lazed target " .. troop.lazeTargetType, 30)
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if cfxGroundTroops.jtacVerbose then
trigger.action.outTextForCoalition(troop.side, troop.name .. " lost sight of lazed target " .. troop.lazeTargetType, 30)
trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
end
troop.lazeTarget = nil
cfxGroundTroops.lazerOff(troop)
troop.lazingUnit = nil
@ -565,8 +505,10 @@ function cfxGroundTroops.updateLaze(troop)
return
else
-- target died
trigger.action.outTextForCoalition(troop.side, troop.name .. " confirms kill for " .. troop.lazeTargetType, 30)
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if cfxGroundTroops.jtacVerbose then
trigger.action.outTextForCoalition(troop.side, troop.name .. " confirms kill for " .. troop.lazeTargetType, 30)
trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
end
troop.lazeTarget = nil
cfxGroundTroops.lazerOff(troop)
troop.lazingUnit = nil
@ -584,8 +526,7 @@ end
function cfxGroundTroops.updateWait(troop)
-- currently nothing to do
-- currently nothing to do
end
function cfxGroundTroops.updateTroops(troop)
@ -616,12 +557,6 @@ function cfxGroundTroops.updateTroops(troop)
end
--
-- we have to systems to process during update:
-- once all, and one per turn, with the exception
-- of lazers, who get updated every turn
--
--
-- all at once
--
@ -754,14 +689,12 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- now, check if still alive
if not dcsCommon.isGroupAlive(group) then
-- group dead, no longer updates
--trigger.action.outText("+++groundT NOTE: <".. troops.group:getName() .."> dead, removing", 30)
cfxGroundTroops.invokeCallbacksFor("dead", troops) -- notify anyone who is interested that we are no longer proccing these
cfxGroundTroops.removeTroopsFromPool(troops)
return -- nothing else to do
end
-- now, execute the update itself, standard update
--trigger.action.outText("+++groundT: singleU troop <".. troops.group:getName() .."> with orders <" .. troops.orders .. ">", 30)
cfxGroundTroops.updateTroops(troops)
-- check max speed of group. if < 0.1 then note and increase
@ -775,7 +708,6 @@ function cfxGroundTroops.updateSingleScheduled(params)
if troops.speedWarning > 5 then -- make me 5
lastOrder = timer.getTime() - troops.lastOrderDate
--trigger.action.outText("+++groundT WARNING: <".. troops.group:getName() .."> (S:".. troops.side .. ") to " .. troops.destination.name .. ": stopped for " .. troops.speedWarning .. " iters, orderage=" .. lastOrder, 30)
-- this may be a matter of too many waypoints.
-- maybe issue orders to go to their destination directly?
-- now force an order to go directly.
@ -784,17 +716,15 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- we already switched to off-road. take me
-- out of the managed queue, I'm not going
-- anywhere
-- trigger.action.outText("+++groundT <".. troops.group:getName() .."> is going nowhere. Removed from managed troops", 30)
cfxGroundTroops.removeTroopsFromPool(troops)
else
cfxGroundTroops.switchToOffroad(troops)
-- trigger.action.outText("+++groundT <".. troops.group:getName() .."> SWITCHED TO OFFROAD", 30)
troops.isOffroad = true -- so we know that we already did that
end
end
end
-- now reschedule updte for my best time
-- now reschedule update for my best time
local updateTime = cfxGroundTroops.getScheduleInterval(troops.orders)
troops.updateID = timer.scheduleFunction(cfxGroundTroops.updateSingleScheduled, params, timer.getTime() + updateTime)
end
@ -818,11 +748,9 @@ end
function cfxGroundTroops.checkPileUp()
-- schedule my next call
--trigger.action.outText("+++groundT: pileup check", 30)
timer.scheduleFunction(cfxGroundTroops.checkPileUp, {}, timer.getTime() + 60)
local thePiles = {}
if not cfxOwnedZones then
-- trigger.action.outText("+++groundT: pileUp - owned zones not yet ready", 30)
return
end
@ -945,11 +873,6 @@ function cfxGroundTroops.getTroopReport(theSide, ignoreInfantry)
return report
end
--
-- CREATE / ADD / REMOVE
--
--
-- createGroundTroop
-- use this to create a cfxGroundTroops from a dcs group
@ -1100,13 +1023,12 @@ function cfxGroundTroops.manageQueues()
-- if we here, there are items waiting in the queue
while dcsCommon.getSizeOfTable(cfxGroundTroops.deployedTroops) < cfxGroundTroops.maxManagedTroops and #cfxGroundTroops.troopQueue > 0 do
-- trnasfer items from the front to the managed queue
-- transfer items from the front to the managed queue
local theTroops = cfxGroundTroops.troopQueue[1]
table.remove(cfxGroundTroops.troopQueue, 1)
if theTroops.group:isExist() then
cfxGroundTroops.deployedTroops[theTroops.group:getName()] = theTroops
end
-- trigger.action.outText("+++gT: dequed and activaed " .. theTroops.group:getName(), 30)
end
end

View File

@ -126,6 +126,7 @@ end
function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
if not trackerName then
trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30)
return
end
if not theGroup then
trigger.action.outText("+++gTrk: no group in addGroupToTrackerNamed <" .. trackerName .. ">", 30)
@ -543,6 +544,13 @@ function groupTracker.trackGroupsInZone(theZone)
local trackerName = cfxZones.getStringFromZoneProperty(theZone, "addToTracker:", "<none>")
local theGroups = cfxZones.allGroupsInZone(theZone, nil)
--[[-- trigger.action.outText("Groups in zone <" .. theZone.name .. ">:", 30)
local msg = " :: "
for idx, aGroup in pairs (theGroups) do
msg = msg .. " <" .. aGroup:getName() .. ">"
end
trigger.action.outText(msg, 30)
--]]--
-- now init array processing
local trackerNames = {}
@ -613,6 +621,18 @@ function groupTracker.start()
groupTracker.trackGroupsInZone(aZone) -- process attributes
end
-- verbose debugging:
-- show who's tracking who
for idx, theZone in pairs(groupTracker.trackers) do
if groupTracker.verbose or theZone.verbose then
local msg = " - Tracker <" .. theZone.name .. ">: "
for idx, theGroup in pairs(theZone.trackedGroups) do
msg = msg .. "<" .. theGroup:getName() .. "> "
end
trigger.action.outText(msg, 30)
end
end
-- update all cloners and spawned clones from file
if persistence then
-- sign up for persistence

View File

@ -1,15 +1,24 @@
jtacGrpUI = {}
jtacGrpUI.version = "1.0.2"
jtacGrpUI.version = "2.0.0"
jtacGrpUI.requiredLibs = {
"dcsCommon", -- always
"cfxZones",
"cfxGroundTroops",
}
--[[-- VERSION HISTORY
- 1.0.2 - also include idling JTACS
- add positional info when using owned zones
- 2.0.0 - dmlZones
- sanity checks upon load
- eliminated cfxPlayer dependence
- clean-up
- jtacSound
--]]--
-- find & command cfxGroundTroops-based jtacs
-- UI installed via OTHER for all groups with players
-- module based on xxxGrpUI
jtacGrpUI.groupConfig = {} -- all inited group private config data
jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name.
jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
--
@ -24,6 +33,9 @@ function jtacGrpUI.resetConfig(conf)
end
function jtacGrpUI.createDefaultConfig(theGroup)
if not theGroup then return nil end
if not Group.isExist(theGroup) then return end
local conf = {}
conf.theGroup = theGroup
conf.name = theGroup:getName()
@ -32,8 +44,8 @@ function jtacGrpUI.createDefaultConfig(theGroup)
jtacGrpUI.resetConfig(conf)
conf.mainMenu = nil; -- this is where we store the main menu if we branch
conf.myCommands = nil; -- this is where we store the commands if we branch
conf.mainMenu = nil; -- root
conf.myCommands = nil; -- commands branch
return conf
end
@ -41,15 +53,15 @@ end
-- getConfigFor group will allocate if doesn't exist in DB
-- and add to it
function jtacGrpUI.getConfigForGroup(theGroup)
if not theGroup then
if not theGroup or (not Group.isExist(theGroup))then
trigger.action.outText("+++WARNING: jtacGrpUI nil group in getConfigForGroup!", 30)
return nil
end
local theName = theGroup:getName()
local c = jtacGrpUI.getConfigByGroupName(theName) -- we use central accessor
local c = jtacGrpUI.getConfigByGroupName(theName)
if not c then
c = jtacGrpUI.createDefaultConfig(theGroup)
jtacGrpUI.groupConfig[theName] = c -- should use central accessor...
jtacGrpUI.groupConfig[theName] = c
end
return c
end
@ -66,16 +78,13 @@ function jtacGrpUI.getConfigForUnit(theUnit)
trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30)
return nil
end
local theGroup = theUnit:getGroup()
return getConfigForGroup(theGroup)
end
--
--
-- M E N U H A N D L I N G
-- =========================
--
--
function jtacGrpUI.clearCommsSubmenus(conf)
if conf.myCommands then
@ -95,8 +104,7 @@ function jtacGrpUI.removeCommsFromConfig(conf)
end
end
-- this only works in single-unit groups. may want to check if group
-- has disappeared
-- this only works in single-unit player groups.
function jtacGrpUI.removeCommsForUnit(theUnit)
if not theUnit then return end
if not theUnit:isExist() then return end
@ -112,11 +120,21 @@ function jtacGrpUI.removeCommsForGroup(theGroup)
jtacGrpUI.removeCommsFromConfig(conf)
end
--
-- set main root in F10 Other. All sub menus click into this
--
function jtacGrpUI.isEligibleForMenu(theGroup)
return true
if jtacGrpUI.jtacTypes == "all" or
jtacGrpUI.jtacTypes == "any" then return true end
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "hel", true) then
local cat = theGroup:getCategory()
return cat == 1
end
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "plan", true) then
local cat = theGroup:getCategory()
return cat == 0
end
if jtacGrpUI.verbose then
trigger.action.outText("+++jGUI: unknown jtacTypes <" .. jtacGrpUI.jtacTypes .. "> -- allowing access to group <" .. theGroup:getName() ..">", 30)
end
return true -- for later expansion
end
function jtacGrpUI.setCommsMenuForUnit(theUnit)
@ -131,38 +149,25 @@ function jtacGrpUI.setCommsMenuForUnit(theUnit)
end
function jtacGrpUI.setCommsMenu(theGroup)
-- depending on own load state, we set the command structure
-- it begins at 10-other, and has 'jtac' as main menu with submenus
-- as required
if not theGroup then return end
if not theGroup:isExist() then return end
-- we test here if this group qualifies for
-- the menu. if not, exit
if not Group.isExist(theGroup) then return end
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash
-- trigger.action.outText("+++ setting group <".. conf.theGroup:getName() .. "> jtac command", 30)
conf.id = theGroup:getID(); -- we always do this ALWAYS
if jtacGrpUI.simpleCommands then
-- we install directly in F-10 other
if not conf.myMainMenu then
local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
nil,
jtacGrpUI.redirectCommandX,
{conf, "lasing report"}
)
conf.id, commandTxt, nil, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
conf.myMainMenu = theCommand
end
return
end
-- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac')
@ -178,12 +183,6 @@ function jtacGrpUI.setCommsMenu(theGroup)
end
function jtacGrpUI.addSubMenus(conf)
-- add menu items to choose from after
-- user clickedf on MAIN MENU. In this implementation
-- they all result invoked methods
local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(
conf.id,
@ -193,24 +192,8 @@ function jtacGrpUI.addSubMenus(conf)
{conf, "lasing report"}
)
table.insert(conf.myCommands, theCommand)
--[[--
commandTxt = "This is another important command"
theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
jtacGrpUI.redirectCommandX,
{conf, "Sub2"}
)
table.insert(conf.myCommands, theCommand)
--]]--
end
--
-- each menu item has a redirect and timed invoke to divorce from the
-- no-debug zone in the menu invocation. Delay is .1 seconds
--
function jtacGrpUI.redirectCommandX(args)
timer.scheduleFunction(jtacGrpUI.doCommandX, args, timer.getTime() + 0.1)
end
@ -219,25 +202,25 @@ function jtacGrpUI.doCommandX(args)
local conf = args[1] -- < conf in here
local what = args[2] -- < second argument in here
local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list
if #targetList < 1 then
trigger.action.outTextForGroup(conf.id, "No targets are currently being lased", 30)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
return
end
local desc = "JTAC Target Report:\n"
-- trigger.action.outTextForGroup(conf.id, "Target Report:", 30)
for i=1, #targetList do
local aTarget = targetList[i]
if aTarget.idle then
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target"
else
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo .." lasing " .. aTarget.lazeTargetType .. " [" .. aTarget.range .. "nm at " .. aTarget.bearing .. "°]"
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo .." lasing " .. aTarget.lazeTargetType .. " [" .. aTarget.range .. "nm at " .. aTarget.bearing .. "°]," .. " code=" .. cfxGroundTroops.laseCode
end
end
trigger.action.outTextForGroup(conf.id, desc .. "\n", 30)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
end
function jtacGrpUI.collectJTACtargets(conf, includeIdle)
@ -249,14 +232,14 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
local theJTACS = {}
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
if troop.coalition == conf.coalition
and troop.orders == "laze"
and troop.lazeTarget
and troop.lazeTarget:isExist()
and troop.orders == "laze"
and troop.lazeTarget
and troop.lazeTarget:isExist()
then
table.insert(theJTACS, troop)
elseif troop.coalition == conf.coalition
and troop.orders == "laze"
and includeIdle
and troop.orders == "laze"
and includeIdle
then
-- we also include idlers
table.insert(theJTACS, troop)
@ -277,7 +260,7 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
local jtacLoc = dcsCommon.getGroupLocation(troop.group)
local nearestZone = cfxOwnedZones.getNearestOwnedZoneToPoint(jtacLoc)
if nearestZone then
local ozRange = dcsCommon.dist(jtacLoc, nearestZone.point) * 0.000621371
local ozRange = dcsCommon.dist(jtacLoc, nearestZone.point) * 0.000621371 -- meters to nm
ozRange = math.floor(ozRange * 10) / 10
local relPos = dcsCommon.compassPositionOfARelativeToB(jtacLoc, nearestZone.point)
aTarget.posInfo = " (" .. ozRange .. "nm " .. relPos .. " of " .. nearestZone.name .. ")"
@ -295,7 +278,6 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
aTarget.range = aTarget.range * 0.000621371 -- meter to miles
aTarget.range = math.floor(aTarget.range * 10) / 10
aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there)
--aTarget.jtacName = troop.name
aTarget.lazeTargetType = troop.lazeTargetType
end
table.insert(targetList, aTarget)
@ -309,87 +291,82 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
end
--
-- G R O U P M A N A G E M E N T
-- event handler - simplified, only for player birth
--
-- Group Management is required to make sure all groups
-- receive a comms menu and that they receive a clean-up
-- when required
--
-- Callbacks are provided by cfxPlayer module to which we
-- subscribe during init
--
function jtacGrpUI.playerChangeEvent(evType, description, player, data)
--trigger.action.outText("+++ groupUI: received <".. evType .. "> Event", 30)
if evType == "newGroup" then
-- initialized attributes are in data as follows
-- .group - new group
-- .name - new group's name
-- .primeUnit - the unit that trigggered new group appearing
-- .primeUnitName - name of prime unit
-- .id group ID
--theUnit = data.primeUnit
jtacGrpUI.setCommsMenu(data.group)
-- trigger.action.outText("+++ groupUI: added " .. theUnit:getName() .. " to comms menu", 30)
return
end
if evType == "removeGroup" then
-- data is the player record that no longer exists. it consists of
-- .name
-- we must remove the comms menu for this group else we try to add another one to this group later
local conf = jtacGrpUI.getConfigByGroupName(data.name)
if conf then
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
else
trigger.action.outText("+++ jtacUI: can't retrieve group <" .. data.name .. "> config: not found!", 30)
end
return
end
if evType == "leave" then
-- player unit left. we don't care since we only work on group level
-- if they were the only, this is followed up by group disappeared
end
if evType == "unit" then
-- player changed units. almost never in MP, but possible in solo
-- because of 1 seconds timing loop
-- will result in a new group appearing and a group disappearing, so we are good
-- may need some logic to clean up old configs and/or menu items
end
function jtacGrpUI:onEvent(theEvent)
if not theEvent then return end
local theUnit = theEvent.initiator
if not theUnit then return end
local uName = theUnit:getName()
if not theUnit.getPlayerName then return end
if not theUnit:getPlayerName() then return end
-- we now have a player birth event.
local pName = theUnit:getPlayerName()
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gName = theGroup:getName()
if not gName then return end
if jtacGrpUI.verbose then
trigger.action.outText("+++jGUI: birth player. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30)
end
local conf = jtacGrpUI.getConfigByGroupName(gName)
if conf then
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
end
jtacGrpUI.setCommsMenu(theGroup)
end
--
-- Start
--
function jtacGrpUI.start()
function jtacGrpUI.readConfigZone()
local theZone = cfxZones.getZoneByName("jtacGrpUIConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("jtacGrpUIConfig")
end
jtacGrpUI.jtacTypes = theZone:getStringFromZoneProperty("jtacTypes", "all")
jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes)
jtacGrpUI.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
jtacGrpUI.verbose = theZone.verbose
-- iterate existing groups so we have a start situation
-- now iterate through all player groups and install the Assault Troop Menu
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
-- contains per group player record. Does not resolve on unit level!
for gname, pgroup in pairs(allPlayerGroups) do
local theUnit = pgroup.primeUnit -- get any unit of that group
jtacGrpUI.setCommsMenuForUnit(theUnit) -- set up
end
-- now install the new group notifier to install Assault Troops menu
cfxPlayer.addMonitor(jtacGrpUI.playerChangeEvent)
trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30)
end
--
function jtacGrpUI.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx jtac GUI requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx jtac GUI", jtacGrpUI.requiredLibs) then
return false
end
jtacGrpUI.readConfigZone()
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for unitName, theUnit in pairs(allPlayerUnits) do
jtacGrpUI.setCommsMenuForUnit(theUnit)
end
-- now install event handler
world.addEventHandler(jtacGrpUI)
trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30)
return true
end
-- GO GO GO
--
if not cfxGroundTroops then
trigger.action.outText("cf/x jtacGrpUI REQUIRES cfxGroundTroops to work.", 30)
else
jtacGrpUI.start()
end
if not jtacGrpUI.start() then
trigger.action.outText("JTAC GUI failed to start up.", 30)
jtacGrpUI = nil
end
--[[--
TODO:
callback into GroundTroops lazing
what is 'simpleCommand' really for? remove or refine
--]]--

333
modules/milHelo.lua Normal file
View File

@ -0,0 +1,333 @@
milHelo = {}
milHelo.version = "0.0.0"
milHelo.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
}
milHelo.zones = {}
milHelo.targets = {}
milHelo.ups = 1
function milHelo.addMilHeloZone(theZone)
milHelo.zones[theZone.name] = theZone
end
function milHelo.addMilTargetZone(theZone)
milHelo.targets[theZone.name] = theZone
end
function milHelo.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
local zP = cfxZones.getPoint(theZone)
zP = theZone:getDCSOrigin() -- don't use getPoint now.
zP.y = 0
for idx, aUnit in pairs(theUnits) do
local uP = {}
uP.x = aUnit.x
uP.y = 0
uP.z = aUnit.y -- !! y-z
if theZone:pointInZone(uP) then return true end
end
return false
end
function milHelo.allGroupsInZoneByData(theZone) -- move to MX?
local theGroupsInZone = {}
local count = 0
for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then
if milHelo.partOfGroupDataInZone(theZone, groupData.units) then
theGroupsInZone[groupName] = groupData -- DATA! work on clones!
count = count + 1
if theZone.verbose then
trigger.action.outText("+++milH: added group <" .. groupName .. "> for zone <" .. theZone.name .. ">", 30)
end
end
end
end
return theGroupsInZone, count
end
function milHelo.readMilHeloZone(theZone) -- process attributes
-- get mission type. part of milHelo
theZone.msnType = string.lower(theZone:getStringFromZoneProperty("milHelo", "cas"))
-- get all groups inside me
local myGroups, count = milHelo.allGroupsInZoneByData(theZone)
theZone.myGroups = myGroups
theZone.groupCount = count
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.hot = theZone:getBoolFromZoneProperty("hot", true)
theZone.speed = theZone:getNumberFromZoneProperty("speed", 50) -- 110 mph
theZone.alt = theZone:getNumberFromZoneProperty("alt", 100) -- we are always radar alt
-- wipe all existing
for groupName, data in pairs(myGroups) do
local g = Group.getByName(groupName)
if g then
Group.destroy(g)
end
end
if theZone.verbose or milHelo.verbose then
trigger.action.outText("+++milH: processed milHelo zone <" .. theZone.name .. ">", 30)
end
end
function milHelo.readMilTargetZone(theZone)
if theZone.verbose or milHelo.verbose then
trigger.action.outText("+++milH: processed TARGET zone <" .. theZone.name .. ">", 30)
end
end
--
-- Spawning for a zone
--
--[[--
function milHelo.getNthItem(theSet, n)
local count = 1
for key, value in pairs(theSet) do
if count == n then return value end
count = count + 1
end
return nil
end
--]]--
function milHelo.createCASTask(num, auto)
if not auto then auto = false end
if not num then num = 1 end
local task = {}
task.number = num
task.key = "CAS"
task.id = "EngageTargets"
task.enabled = true
task.auto = auto
local params = {}
params.priority = 0
-- params.targetTypes = {"Helicopters", "Ground Units", "Light armed ships"}
local targetTypes = {[1] = "Helicopters", [2] = "Ground Units", [3] = "Light armed ships",}
params.targetTypes = targetTypes
task.params = params
return task
end
function milHelo.createROETask(num, roe)
if not num then num = 1 end
if not roe then roe = 0 end
local task = {}
task.number = num
task.enabled = true
task.auto = false
task.id = "WrappedAction"
local params = {}
local action = {}
action.id = "Option"
local p2 = {}
p2.value = roe -- 0 = Weapons free
p2.name = 0 -- name 0 = ROE
action.params = p2
params.action = action
task.params = params
return task
end
function milHelo.createOrbitTask(num, duration, theZone)
if not num then num = 1 end
local task = {}
task.number = num
task.auto = false
task.id = "ControlledTask"
task.enabled = true
local params = {}
local t2 = {}
t2.id = "Orbit"
local p2 = {}
p2.altitude = theZone.alt
p2.pattern = "Circle"
p2.speed = theZone.speed
p2.altitudeEdited = true
t2.params = p2
params.task = t2
params.stopCondition = {}
params.stopCondition.duration = duration
task.params = params
return task
end
function milHelo.createTakeOffWP(theZone)
local WP = {}
WP.alt = theZone.alt
WP.alt_type = "RADIO"
WP.properties = {}
WP.properties.addopt = {}
WP.action = "From Ground Area"
if theZone.hot then WP.action = "From Ground Area Hot" end
WP.speed = theZone.speed
WP.task = {}
WP.task.id = "ComboTask"
WP.task.params = {}
local tasks = {}
-- local casTask = milHelo.createCASTask(1)
-- tasks[1] = casTask
local roeTask = milHelo.createROETask(1,0) -- 0 = weapons free
tasks[1] = roeTask
WP.task.params.tasks = tasks
--
WP.type = "TakeOffGround"
if theZone.hot then WP.type = "TakeOffGroundHot" end
p = theZone:getPoint()
WP.x = p.x
WP.y = p.z
WP.ETA = 0
WP.ETA_locked = false
WP.speed_locked = true
WP.formation_template = ""
return WP
end
function milHelo.createOrbitWP(theZone, targetPoint)
local WP = {}
WP.alt = theZone.alt
WP.alt_type = "RADIO"
WP.properties = {}
WP.properties.addopt = {}
WP.action = "Turning Point"
WP.speed = theZone.speed
WP.task = {}
WP.task.id = "ComboTask"
WP.task.params = {}
-- start params construct
local tasks = {}
local casTask = milHelo.createCASTask(1, false)
tasks[1] = casTask
local oTask = milHelo.createOrbitTask(2, 3600, theZone)
tasks[2] = oTask
WP.task.params.tasks = tasks
WP.type = "Turning Point"
WP.x = targetPoint.x
WP.y = targetPoint.z
WP.ETA = 0
WP.ETA_locked = false
WP.speed_locked = true
WP.formation_template = ""
return WP
end
function milHelo.spawnForZone(theZone, targetZone)
local theRawData = dcsCommon.getNthItem(theZone.myGroups, 1)
local gData = dcsCommon.clone(theRawData)
--[[--
-- pre-process gData: names, id etc
gData.name = dcsCommon.uuid(gData.name)
for idx, uData in pairs(gData.units) do
uData.name = dcsCommon.uuid(uData.name)
end
gData.groupId = nil
-- change task according to missionType in Zone
gData.task = "CAS"
-- create and process route
local route = {}
route.points = {}
-- gData.route = route
-- create take-off waypoint
local wpTOff = milHelo.createTakeOffWP(theZone)
-- depending on mission, create an orbit or land WP
local dest = targetZone:getPoint()
local wpDest = milHelo.createOrbitWP(theZone, dest)
-- move group to WP1 and add WP1 and WP2 to route
-- dcsCommon.moveGroupDataTo(theGroup,
-- fromWP.x,
-- fromWP.y)
----
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
dcsCommon.addRoutePointForGroupData(gData, wpDest)
--]]--
dcsCommon.dumpVar2Str("route", gData.route)
-- make it a cty
if theZone.coa == 0 then
trigger.action.outText("+++milH: WARNING - zone <" .. theZone.name .. "> is NEUTRAL", 30)
end
local cty = dcsCommon.getACountryForCoalition(theZone.coa)
-- spawn
local groupCat = Group.Category.HELICOPTER
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
return theSpawnedGroup, gData
end
--
-- update and event
--
function milHelo.update()
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1)
end
function milHelo.onEvent(theEvent)
end
--
-- Config & start
--
function milHelo.readConfigZone()
local theZone = cfxZones.getZoneByName("milHeloConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("milHeloConfig")
end
milHelo.verbose = theZone.verbose
end
function milHelo.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx civ helo requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil helo", milHelo.requiredLibs) then
return false
end
-- read config
milHelo.readConfigZone()
-- process milHelo Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("milHelo")
for k, aZone in pairs(attrZones) do
milHelo.readMilHeloZone(aZone) -- process attributes
milHelo.addMilHeloZone(aZone) -- add to list
end
attrZones = cfxZones.getZonesWithAttributeNamed("milTarget")
for k, aZone in pairs(attrZones) do
milHelo.readMilTargetZone(aZone) -- process attributes
milHelo.addMilTargetZone(aZone) -- add to list
end
-- start update in 5 seconds
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1/milHelo.ups)
-- install event handler
world.addEventHandler(milHelo)
-- say hi
trigger.action.outText("milHelo v" .. milHelo.version .. " started.", 30)
return true
end
if not milHelo.start() then
trigger.action.outText("milHelo failed to start.", 30)
milHelo = nil
end
-- do some one-time stuff
local theZone = dcsCommon.getFirstItem(milHelo.zones)
local targetZone = dcsCommon.getFirstItem(milHelo.targets)
milHelo.spawnForZone(theZone, targetZone)

View File

@ -1,5 +1,5 @@
unGrief = {}
unGrief.version = "1.2.0"
unGrief.version = "2.0.0"
unGrief.verbose = false
unGrief.ups = 1
unGrief.requiredLibs = {
@ -21,6 +21,11 @@ unGrief.disabledFlagValue = unGrief.enabledFlagValue + 100 -- DO NOT CHANGE
- strict rules
- warnings on enter/exit
- warnings optional
2.0.0 - dmlZones
- also trigger on birth event, more wrathful
- auto-turn on ssb when retaliation is SSB
- re-open slot after kick in 15 seconds
--]]--
@ -55,13 +60,20 @@ function unGrief.createPvpWithZone(theZone)
trigger.action.outText("+++uGrf: <" .. theZone.name .. "> is designated as PVP legal", 30)
end
theZone.strictPVP = cfxZones.getBoolFromZoneProperty(theZone, "strict", false)
theZone.strictPVP = theZone:getBoolFromZoneProperty("strict", false)
end
-- vengeance: if player killed before, they are no longer welcome
function unGrief.reconcile(groupName)
-- re-open slot after player was kicked
trigger.action.setUserFlag(groupName, unGrief.enabledFlagValue)
trigger.action.outText("Group <" .. groupName .. "> now available again after pest control action", 30)
end
function unGrief.exactVengance(theEvent)
if theEvent.id == 20 then -- S_EVENT_PLAYER_ENTER_UNIT
if theEvent.id == 20 or -- S_EVENT_PLAYER_ENTER_UNIT
theEvent.id == 15 then -- Birth
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not theUnit.getPlayerName then return end -- wierd stuff happening here
@ -94,6 +106,7 @@ function unGrief.exactVengance(theEvent)
-- tell ssb to kick now:
trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue)
trigger.action.outText("Player <" .. playerName .. "> is not welcome here. Shoo! Shoo!", 30)
timer.scheduleFunction(unGrief.reconcile, groupName, timer.getTime() + 15)
return
end
@ -200,6 +213,7 @@ function unGrief:onEvent(theEvent)
local groupName = theGroup:getName()
-- tell ssb to kick now:
trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue)
timer.scheduleFunction(unGrief.reconcile, groupName, timer.getTime() + 15)
return
end
-- aaand all your base are belong to us!
@ -256,23 +270,27 @@ function unGrief.readConfigZone()
theZone = cfxZone.createSimpleZone("unGriefConfig")
end
unGrief.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
unGrief.verbose = theZone.verbose
unGrief.graceKills = cfxZones.getNumberFromZoneProperty(theZone, "graceKills", 1)
unGrief.retaliation = cfxZones.getStringFromZoneProperty(theZone, "retaliation", "boom") -- other possible methods: ssb
unGrief.graceKills = theZone:getNumberFromZoneProperty("graceKills", 1)
unGrief.retaliation = theZone:getStringFromZoneProperty("retaliation", "boom") -- other possible methods: ssb
unGrief.retaliation = dcsCommon.trim(unGrief.retaliation:lower())
unGrief.wrathful = cfxZones.getBoolFromZoneProperty(theZone, "wrathful", false)
unGrief.pve = cfxZones.getBoolFromZoneProperty(theZone, "pve", false)
if cfxZones.hasProperty(theZone, "pveOnly") then
unGrief.pve = cfxZones.getBoolFromZoneProperty(theZone, "pveOnly", false)
if unGrief.retaliation == "ssb" then
-- now turn on ssb
trigger.action.setUserFlag("SSB",100)
trigger.action.outText("unGrief: SSB enabled for retaliation.", 30)
end
unGrief.ignoreAI = cfxZones.getBoolFromZoneProperty(theZone, "ignoreAI", false)
unGrief.wrathful = theZone:getBoolFromZoneProperty("wrathful", false)
unGrief.PVPwarnings = cfxZones.getBoolFromZoneProperty(theZone, "warnings", true)
unGrief.pve = theZone:getBoolFromZoneProperty("pve", false)
if theZone:hasProperty("pveOnly") then
unGrief.pve = theZone:getBoolFromZoneProperty("pveOnly", false)
end
unGrief.ignoreAI = theZone:getBoolFromZoneProperty("ignoreAI", false)
unGrief.PVPwarnings = theZone:getBoolFromZoneProperty("warnings", true)
if unGrief.verbose then
trigger.action.outText("+++uGrf: read config", 30)