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 = {}
cfxZones.version = "4.3.0" cfxZones.version = "4.3.1"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- 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.1.2 - hash property missing warning
- 4.2.0 - new createRandomPointInPopulatedZone() - 4.2.0 - new createRandomPointInPopulatedZone()
- 4.3.0 - boolean supports maybe, random, rnd, ? - 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 if not aGroup:isExist() then return false end
local allUnits = aGroup:getUnits() local allUnits = aGroup:getUnits()
for uk, aUnit in pairs (allUnits) do 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 p = aUnit:getPoint()
local inzone, percent, dist = cfxZones.pointInZone(p, aZone) local inzone, percent, dist = cfxZones.pointInZone(p, aZone)
if inzone then if inzone then
@ -2244,8 +2246,9 @@ end
function cfxZones.randomDelayFromPositiveRange(minVal, maxVal) -- should be moved to dcsCommon function cfxZones.randomDelayFromPositiveRange(minVal, maxVal) -- should be moved to dcsCommon
if not maxVal then return minVal end if not maxVal then return minVal end
if not minVal then return maxVal end if not minVal then return maxVal end
if minVal == maxVal then return minVal end
local delay = maxVal 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 -- we want a randomized from time from minTime .. delay
local varPart = delay - minVal + 1 local varPart = delay - minVal + 1
varPart = dcsCommon.smallRandom(varPart) - 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.x = aUnit.x
uP.y = 0 uP.y = 0
uP.z = aUnit.y -- !! y-z 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 if theZone:pointInZone(uP) then return true end
end end
return false return false
@ -107,7 +105,6 @@ end
function cloneZones.allGroupsInZoneByData(theZone) function cloneZones.allGroupsInZoneByData(theZone)
local theGroupsInZone = {} local theGroupsInZone = {}
local radius = theZone.radius
for groupName, groupData in pairs(cfxMX.groupDataByName) do for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then if groupData.units then
if cloneZones.partOfGroupDataInZone(theZone, groupData.units) then if cloneZones.partOfGroupDataInZone(theZone, groupData.units) then

View File

@ -4,7 +4,7 @@
-- *** EXTENDS ZONES: 'pathing' attribute -- *** EXTENDS ZONES: 'pathing' attribute
-- --
cfxCommander = {} cfxCommander = {}
cfxCommander.version = "1.1.3" cfxCommander.version = "1.1.4"
--[[-- 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
@ -29,6 +29,7 @@ cfxCommander.version = "1.1.3"
- added delay defaulting for most scheduling functions - added delay defaulting for most scheduling functions
- 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()
--]]-- --]]--
@ -337,6 +338,18 @@ function cfxCommander.makeGroupGoThere(group, there, speed, formation, delay)
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 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) local wp = cfxCommander.createWPListForGroupToPoint(group, there, speed, formation)
cfxCommander.assignWPListToGroup(group, wp, delay) 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.4 - pass theZone with missionCreateCB when created from zone
3.2.5 - smoke callbacks 3.2.5 - smoke callbacks
- useRanks option - useRanks option
3.2.6 - inBuiltup analogon to cloner
INTEGRATES AUTOMATICALLY WITH playerScore INTEGRATES AUTOMATICALLY WITH playerScore
@ -1277,7 +1279,7 @@ function csarManager.createCSARMissionFromZone(theZone)
if theZone.onRoad then if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z) mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
elseif theZone.inPopulated then 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 mPoint = aPoint -- safety in case we need to mod aPoint
end end
local theMission = csarManager.createCSARMissionData( local theMission = csarManager.createCSARMissionData(
@ -1409,12 +1411,28 @@ function csarManager.readCSARZone(theZone)
theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true) theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true)
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false) theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
theZone.inPopulated = theZone:getBoolFromZoneProperty("inPopulated", false) if theZone:hasProperty("onRoads") then
theZone.clearance = theZone:getNumberFromZoneProperty("clearance", 10) theZone.onRoad = theZone:getBoolFromZoneProperty("onRoads", false)
theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20) 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 if theZone.onRoad and theZone.inPopulated then
trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30) trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30)
theZone.inPopulated = false
end end
-- add to list of startable csar -- add to list of startable csar
@ -1423,27 +1441,6 @@ function csarManager.readCSARZone(theZone)
end end
if (not deferred) then 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) local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission, theZone) csarManager.addMission(theMission, theZone)
end end

View File

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

View File

@ -1,5 +1,5 @@
groundExplosion = {} groundExplosion = {}
groundExplosion.version = "1.0.0" groundExplosion.version = "1.1.0"
groundExplosion.requiredLibs = { groundExplosion.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@ -9,6 +9,8 @@ groundExplosion.zones = {}
--[[-- --[[--
Version History Version History
1.0.0 - Initial version 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 end
theZone.duration = theZone:getNumberFromZoneProperty("duration", 0) theZone.duration = theZone:getNumberFromZoneProperty("duration", 0)
theZone.aglMin, theZone.aglMax = theZone:getPositiveRangeFromZoneProperty("AGL", 1,1) 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 end
-- --
@ -38,6 +43,16 @@ function groundExplosion.doBoom(args)
local power = args[2] local power = args[2]
local theZone = args[3] local theZone = args[3]
trigger.action.explosion(loc, power) 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 end
function groundExplosion.startBoom(theZone) function groundExplosion.startBoom(theZone)
@ -82,7 +97,7 @@ end
function groundExplosion.start() function groundExplosion.start()
if not dcsCommon.libCheck("cfx groundExplosion", if not dcsCommon.libCheck("cfx groundExplosion",
cfxObjectDestructDetector.requiredLibs) then groundExplosion.requiredLibs) then
return false return false
end end

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {} cfxGroundTroops = {}
cfxGroundTroops.version = "1.7.8" cfxGroundTroops.version = "2.0.0"
cfxGroundTroops.ups = 1 cfxGroundTroops.ups = 1
cfxGroundTroops.verbose = false cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = { cfxGroundTroops.requiredLibs = {
@ -22,52 +22,15 @@ cfxGroundTroops.requiredLibs = {
-- module -- module
cfxGroundTroops.deployedTroops = {} -- indexed by group name cfxGroundTroops.deployedTroops = {} -- indexed by group name
cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
--[[-- --[[--
version history version history
1.3.0 - added "wait-" prefix to have toops do nothing
- added lazing 2.0.0 - dmlZones
1.3.1 - sound for lazing msg is "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" - jtacSound
- lazing --> lasing in text - clanup
1.3.2 - set ups to 2 - jtacVerbose
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
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
@ -113,63 +76,47 @@ cfxGroundTroops.deployedTroops = {} -- indexed by group name
-- queued will work one every pass (except for lazed), distributing the load much better -- 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 -- 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 function cfxGroundTroops.invokeCallbacks(ID, jtac, tgt, data)
cfxGroundTroops.scheduledUpdates = true -- set to false to allow queing of standard updates. overrides queuedUpdates -- IS is aqui, lost, dead, jtac died. jtac is group, tgt is unit, data is rest
cfxGroundTroops.monitorNumbers = false -- set to true to debug managed group size for idx, cb in pairs(cfxGroundTroops.jtacCB) do
cb(ID, jtac, tgt, data)
end
end
cfxGroundTroops.standardScheduleInterval = 30 -- 30 seconds between calls function cfxGroundTroops.addJtacCB(theCB)
cfxGroundTroops.guardUpdateInterval = 30 -- every 30 seconds we check up on guards table.insert(cfxGroundTroops.jtacCB, theCB)
cfxGroundTroops.trackingUpdateInterval = 0.5 -- 0.5 seconds for lazer tracking etc 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 cfxGroundTroops.troopQueue = {} -- FIFO stack
-- return the best tracking interval for this type of orders -- return the best tracking interval for this type of orders
--
-- READ CONFIG ZONE TO OVERRIDE SETTING
--
function cfxGroundTroops.readConfigZone() function cfxGroundTroops.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("groundTroopsConfig") local theZone = cfxZones.getZoneByName("groundTroopsConfig")
if not theZone then if not theZone then
if cfxGroundTroops.verbose then
trigger.action.outText("***gndT: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("groundTroopsConfig") theZone = cfxZones.createSimpleZone("groundTroopsConfig")
end end
-- ok, for each property, load it if it exists cfxGroundTroops.queuedUpdates = theZone:getBoolFromZoneProperty("queuedUpdates", false)
if cfxZones.hasProperty(theZone, "queuedUpdates") then cfxGroundTroops.scheduledUpdates = theZone:getBoolFromZoneProperty("scheduledUpdates", false)
cfxGroundTroops.queuedUpdates = cfxZones.getBoolFromZoneProperty(theZone, "queuedUpdates", false) cfxGroundTroops.maxManagedTroops = theZone:getNumberFromZoneProperty("maxManagedTroops", 67)
end cfxGroundTroops.monitorNumbers = theZone:getBoolFromZoneProperty("monitorNumbers", false)
cfxGroundTroops.standardScheduleInterval = theZone:getNumberFromZoneProperty("standardScheduleInterval", 30)
if cfxZones.hasProperty(theZone, "scheduledUpdates") then cfxGroundTroops.guardUpdateInterval = theZone:getNumberFromZoneProperty("guardUpdateInterval", 30)
cfxGroundTroops.scheduledUpdates = cfxZones.getBoolFromZoneProperty(theZone, "scheduledUpdates", false) cfxGroundTroops.trackingUpdateInterval = theZone:getNumberFromZoneProperty("trackingUpdateInterval", 0.5)
end
cfxGroundTroops.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if cfxZones.hasProperty(theZone, "maxManagedTroops") then cfxGroundTroops.jtacVerbose = theZone:getBoolFromZoneProperty("jtacVerbose", true)
cfxGroundTroops.maxManagedTroops = cfxZones.getNumberFromZoneProperty(theZone, "maxManagedTroops", 65) cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("jtacLaserCode", 1688)
end if theZone:hasProperty("lazeCode") then
cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("lazeCode", 1688)
if cfxZones.hasProperty(theZone, "monitorNumbers") then end
cfxGroundTroops.monitorNumbers = cfxZones.getBoolFromZoneProperty(theZone, "monitorNumbers", false) if theZone:hasProperty("laseCode") then
end cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("laseCode", 1688)
end
if cfxZones.hasProperty(theZone, "standardScheduleInterval") then if theZone:hasProperty("laserCode") then
cfxGroundTroops.standardScheduleInterval = cfxZones.getNumberFromZoneProperty(theZone, "standardScheduleInterval", 30) cfxGroundTroops.laseCode = theZone:getNumberFromZoneProperty("laserCode", 1688)
end end
cfxGroundTroops.verbose = theZone.verbose
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
if cfxGroundTroops.verbose then if cfxGroundTroops.verbose then
trigger.action.outText("+++gndT: read config zone!", 30) trigger.action.outText("+++gndT: read config zone!", 30)
@ -355,8 +302,7 @@ function cfxGroundTroops.updateAttackers(troop)
troop.orders = "guard" troop.orders = "guard"
return return
end end
-- if we get here, we need no change -- if we get here, we need no change
end end
@ -437,7 +383,7 @@ function cfxGroundTroops.findLazeTarget(troop)
-- iterate through the list until we find the first target -- iterate through the list until we find the first target
-- that fits the bill and return it -- that fits the bill and return it
-- trigger.action.outText("+++ looking at " .. #enemyGroups .. " laze groups", 30)
for i=1, #enemyGroups do for i=1, #enemyGroups do
-- get all units for this group -- get all units for this group
local aGroup = enemyGroups[i].group -- remember, they are in a {dist, group} tuple local aGroup = enemyGroups[i].group -- remember, they are in a {dist, group} tuple
@ -448,12 +394,9 @@ function cfxGroundTroops.findLazeTarget(troop)
-- unit lives -- unit lives
-- now, we need to filter infantry. we do this by -- now, we need to filter infantry. we do this by
-- pre-fetching the typeString -- pre-fetching the typeString
--troop.lazeTargetType = aUnit:getTypeName()
-- and checking if the name contains some infantry- -- and checking if the name contains some infantry-
-- typical strings. Idea taken from JTAC script -- typical strings. Idea taken from JTAC script
local isInfantry = dcsCommon.unitIsInfantry(theUnit) local isInfantry = dcsCommon.unitIsInfantry(theUnit)
if not isInfantry then if not isInfantry then
-- this is a vehicle, is it in line of sight? -- this is a vehicle, is it in line of sight?
-- raise the point 2m above ground for both points -- raise the point 2m above ground for both points
@ -466,16 +409,11 @@ function cfxGroundTroops.findLazeTarget(troop)
-- the nearest group to us in range -- the nearest group to us in range
-- that is visible! -- that is visible!
return aUnit return aUnit
else
--trigger.action.outText("+++ ".. aUnit:getName() .."cant be seen", 30)
end -- if visible end -- if visible
else end -- if infantry
-- trigger.action.outText("+++ ".. aUnit:getName() .." (".. troop.lazeTargetType .. ") is infantry", 30)
end -- if not infantry
end -- if alive end -- if alive
end -- for all units end -- for all units
end -- for all enemy groups end -- for all enemy groups
--trigger.action.outText("+++ find nearest laze target did not find anything to laze", 30)
return nil -- no unit found return nil -- no unit found
end end
@ -499,10 +437,12 @@ function cfxGroundTroops.trackLazer(troop)
if not troop.lazerPointer then if not troop.lazerPointer then
local there = troop.lazeTarget:getPoint() 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() troop.lazeTargetType = troop.lazeTarget:getTypeName()
trigger.action.outTextForCoalition(troop.side, troop.name .. " tally target - lasing " .. troop.lazeTargetType .. "!", 30) if cfxGroundTroops.jtacVerbose then
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") 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 troop.lastLazerSpot = there -- remember last spot
local data = {} local data = {}
data.enemy = troop.lazeTarget data.enemy = troop.lazeTarget
@ -510,9 +450,7 @@ function cfxGroundTroops.trackLazer(troop)
cfxGroundTroops.invokeCallbacksFor("lase:tracking", troop, data) cfxGroundTroops.invokeCallbacksFor("lase:tracking", troop, data)
return return
end end
-- if true then return end
-- if we get here, we update the lazerPointer -- if we get here, we update the lazerPointer
local there = troop.lazeTarget:getPoint() local there = troop.lazeTarget:getPoint()
-- we may only want to update the laser spot when dist > trigger -- we may only want to update the laser spot when dist > trigger
@ -531,17 +469,17 @@ function cfxGroundTroops.updateLaze(troop)
else else
cfxGroundTroops.lazerOff(troop) cfxGroundTroops.lazerOff(troop)
troop.lazeTarget = nil troop.lazeTarget = nil
trigger.action.outTextForCoalition(troop.side, troop.name .. " reports lasing " .. troop.lazeTargetType .. " interrupted. Re-acquiring.", 30) if cfxGroundTroops.jtacVerbose then
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") 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 troop.lazingUnit = nil
cfxGroundTroops.invokeCallbacksFor("lase:stop", troop) cfxGroundTroops.invokeCallbacksFor("lase:stop", troop)
return -- we'll re-acquire through a new unit next round return -- we'll re-acquire through a new unit next round
end end
end end
-- if we get here, a lazing unit -- if we get here, a lazing unit
--local here = troop.lazingUnit:getPoint()
if troop.lazeTarget then if troop.lazeTarget then
-- check if that target is alive and in range -- check if that target is alive and in range
if troop.lazeTarget:isExist() and troop.lazeTarget:getLife() >= 1 then if troop.lazeTarget:isExist() and troop.lazeTarget:getLife() >= 1 then
@ -551,8 +489,10 @@ function cfxGroundTroops.updateLaze(troop)
local there = troop.lazeTarget:getPoint() local there = troop.lazeTarget:getPoint()
if dcsCommon.dist(here, there) > troop.range then if dcsCommon.dist(here, there) > troop.range then
-- troop out of range -- troop out of range
trigger.action.outTextForCoalition(troop.side, troop.name .. " lost sight of lazed target " .. troop.lazeTargetType, 30) if cfxGroundTroops.jtacVerbose then
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") 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 troop.lazeTarget = nil
cfxGroundTroops.lazerOff(troop) cfxGroundTroops.lazerOff(troop)
troop.lazingUnit = nil troop.lazingUnit = nil
@ -565,8 +505,10 @@ function cfxGroundTroops.updateLaze(troop)
return return
else else
-- target died -- target died
trigger.action.outTextForCoalition(troop.side, troop.name .. " confirms kill for " .. troop.lazeTargetType, 30) if cfxGroundTroops.jtacVerbose then
trigger.action.outSoundForCoalition(troop.side, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") 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 troop.lazeTarget = nil
cfxGroundTroops.lazerOff(troop) cfxGroundTroops.lazerOff(troop)
troop.lazingUnit = nil troop.lazingUnit = nil
@ -584,8 +526,7 @@ end
function cfxGroundTroops.updateWait(troop) function cfxGroundTroops.updateWait(troop)
-- currently nothing to do -- currently nothing to do
end end
function cfxGroundTroops.updateTroops(troop) function cfxGroundTroops.updateTroops(troop)
@ -616,12 +557,6 @@ function cfxGroundTroops.updateTroops(troop)
end 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 -- all at once
-- --
@ -754,14 +689,12 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- now, check if still alive -- now, check if still alive
if not dcsCommon.isGroupAlive(group) then if not dcsCommon.isGroupAlive(group) then
-- group dead, no longer updates -- 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.invokeCallbacksFor("dead", troops) -- notify anyone who is interested that we are no longer proccing these
cfxGroundTroops.removeTroopsFromPool(troops) cfxGroundTroops.removeTroopsFromPool(troops)
return -- nothing else to do return -- nothing else to do
end end
-- now, execute the update itself, standard update -- now, execute the update itself, standard update
--trigger.action.outText("+++groundT: singleU troop <".. troops.group:getName() .."> with orders <" .. troops.orders .. ">", 30)
cfxGroundTroops.updateTroops(troops) cfxGroundTroops.updateTroops(troops)
-- check max speed of group. if < 0.1 then note and increase -- 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 if troops.speedWarning > 5 then -- make me 5
lastOrder = timer.getTime() - troops.lastOrderDate 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. -- this may be a matter of too many waypoints.
-- maybe issue orders to go to their destination directly? -- maybe issue orders to go to their destination directly?
-- now force an order to go directly. -- now force an order to go directly.
@ -784,17 +716,15 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- we already switched to off-road. take me -- we already switched to off-road. take me
-- out of the managed queue, I'm not going -- out of the managed queue, I'm not going
-- anywhere -- anywhere
-- trigger.action.outText("+++groundT <".. troops.group:getName() .."> is going nowhere. Removed from managed troops", 30)
cfxGroundTroops.removeTroopsFromPool(troops) cfxGroundTroops.removeTroopsFromPool(troops)
else else
cfxGroundTroops.switchToOffroad(troops) 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 troops.isOffroad = true -- so we know that we already did that
end end
end end
end end
-- now reschedule updte for my best time -- now reschedule update for my best time
local updateTime = cfxGroundTroops.getScheduleInterval(troops.orders) local updateTime = cfxGroundTroops.getScheduleInterval(troops.orders)
troops.updateID = timer.scheduleFunction(cfxGroundTroops.updateSingleScheduled, params, timer.getTime() + updateTime) troops.updateID = timer.scheduleFunction(cfxGroundTroops.updateSingleScheduled, params, timer.getTime() + updateTime)
end end
@ -818,11 +748,9 @@ end
function cfxGroundTroops.checkPileUp() function cfxGroundTroops.checkPileUp()
-- schedule my next call -- schedule my next call
--trigger.action.outText("+++groundT: pileup check", 30)
timer.scheduleFunction(cfxGroundTroops.checkPileUp, {}, timer.getTime() + 60) timer.scheduleFunction(cfxGroundTroops.checkPileUp, {}, timer.getTime() + 60)
local thePiles = {} local thePiles = {}
if not cfxOwnedZones then if not cfxOwnedZones then
-- trigger.action.outText("+++groundT: pileUp - owned zones not yet ready", 30)
return return
end end
@ -945,11 +873,6 @@ function cfxGroundTroops.getTroopReport(theSide, ignoreInfantry)
return report return report
end end
--
-- CREATE / ADD / REMOVE
--
-- --
-- createGroundTroop -- createGroundTroop
-- use this to create a cfxGroundTroops from a dcs group -- 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 -- if we here, there are items waiting in the queue
while dcsCommon.getSizeOfTable(cfxGroundTroops.deployedTroops) < cfxGroundTroops.maxManagedTroops and #cfxGroundTroops.troopQueue > 0 do 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] local theTroops = cfxGroundTroops.troopQueue[1]
table.remove(cfxGroundTroops.troopQueue, 1) table.remove(cfxGroundTroops.troopQueue, 1)
if theTroops.group:isExist() then if theTroops.group:isExist() then
cfxGroundTroops.deployedTroops[theTroops.group:getName()] = theTroops cfxGroundTroops.deployedTroops[theTroops.group:getName()] = theTroops
end end
-- trigger.action.outText("+++gT: dequed and activaed " .. theTroops.group:getName(), 30)
end end
end end

View File

@ -126,6 +126,7 @@ end
function groupTracker.addGroupToTrackerNamed(theGroup, trackerName) function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
if not trackerName then if not trackerName then
trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30) trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30)
return
end end
if not theGroup then if not theGroup then
trigger.action.outText("+++gTrk: no group in addGroupToTrackerNamed <" .. trackerName .. ">", 30) 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 trackerName = cfxZones.getStringFromZoneProperty(theZone, "addToTracker:", "<none>")
local theGroups = cfxZones.allGroupsInZone(theZone, nil) 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 -- now init array processing
local trackerNames = {} local trackerNames = {}
@ -613,6 +621,18 @@ function groupTracker.start()
groupTracker.trackGroupsInZone(aZone) -- process attributes groupTracker.trackGroupsInZone(aZone) -- process attributes
end 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 -- update all cloners and spawned clones from file
if persistence then if persistence then
-- sign up for persistence -- sign up for persistence

View File

@ -1,15 +1,24 @@
jtacGrpUI = {} jtacGrpUI = {}
jtacGrpUI.version = "1.0.2" jtacGrpUI.version = "2.0.0"
jtacGrpUI.requiredLibs = {
"dcsCommon", -- always
"cfxZones",
"cfxGroundTroops",
}
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 1.0.2 - also include idling JTACS - 1.0.2 - also include idling JTACS
- add positional info when using owned zones - 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 -- find & command cfxGroundTroops-based jtacs
-- UI installed via OTHER for all groups with players -- UI installed via OTHER for all groups with players
-- module based on xxxGrpUI -- 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 jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
-- --
@ -24,6 +33,9 @@ function jtacGrpUI.resetConfig(conf)
end end
function jtacGrpUI.createDefaultConfig(theGroup) function jtacGrpUI.createDefaultConfig(theGroup)
if not theGroup then return nil end
if not Group.isExist(theGroup) then return end
local conf = {} local conf = {}
conf.theGroup = theGroup conf.theGroup = theGroup
conf.name = theGroup:getName() conf.name = theGroup:getName()
@ -32,8 +44,8 @@ function jtacGrpUI.createDefaultConfig(theGroup)
jtacGrpUI.resetConfig(conf) jtacGrpUI.resetConfig(conf)
conf.mainMenu = nil; -- this is where we store the main menu if we branch conf.mainMenu = nil; -- root
conf.myCommands = nil; -- this is where we store the commands if we branch conf.myCommands = nil; -- commands branch
return conf return conf
end end
@ -41,15 +53,15 @@ end
-- getConfigFor group will allocate if doesn't exist in DB -- getConfigFor group will allocate if doesn't exist in DB
-- and add to it -- and add to it
function jtacGrpUI.getConfigForGroup(theGroup) 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) trigger.action.outText("+++WARNING: jtacGrpUI nil group in getConfigForGroup!", 30)
return nil return nil
end end
local theName = theGroup:getName() local theName = theGroup:getName()
local c = jtacGrpUI.getConfigByGroupName(theName) -- we use central accessor local c = jtacGrpUI.getConfigByGroupName(theName)
if not c then if not c then
c = jtacGrpUI.createDefaultConfig(theGroup) c = jtacGrpUI.createDefaultConfig(theGroup)
jtacGrpUI.groupConfig[theName] = c -- should use central accessor... jtacGrpUI.groupConfig[theName] = c
end end
return c return c
end end
@ -66,16 +78,13 @@ function jtacGrpUI.getConfigForUnit(theUnit)
trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30) trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30)
return nil return nil
end end
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
return getConfigForGroup(theGroup) return getConfigForGroup(theGroup)
end end
--
-- --
-- M E N U H A N D L I N G -- M E N U H A N D L I N G
-- ========================= -- =========================
--
-- --
function jtacGrpUI.clearCommsSubmenus(conf) function jtacGrpUI.clearCommsSubmenus(conf)
if conf.myCommands then if conf.myCommands then
@ -95,8 +104,7 @@ function jtacGrpUI.removeCommsFromConfig(conf)
end end
end end
-- this only works in single-unit groups. may want to check if group -- this only works in single-unit player groups.
-- has disappeared
function jtacGrpUI.removeCommsForUnit(theUnit) function jtacGrpUI.removeCommsForUnit(theUnit)
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
@ -112,11 +120,21 @@ function jtacGrpUI.removeCommsForGroup(theGroup)
jtacGrpUI.removeCommsFromConfig(conf) jtacGrpUI.removeCommsFromConfig(conf)
end end
--
-- set main root in F10 Other. All sub menus click into this
--
function jtacGrpUI.isEligibleForMenu(theGroup) 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 end
function jtacGrpUI.setCommsMenuForUnit(theUnit) function jtacGrpUI.setCommsMenuForUnit(theUnit)
@ -131,38 +149,25 @@ function jtacGrpUI.setCommsMenuForUnit(theUnit)
end end
function jtacGrpUI.setCommsMenu(theGroup) 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 then return end
if not theGroup:isExist() then return end if not Group.isExist(theGroup) then return end
-- we test here if this group qualifies for
-- the menu. if not, exit
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local conf = jtacGrpUI.getConfigForGroup(theGroup) local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash conf.id = theGroup:getID(); -- we always do this ALWAYS
-- trigger.action.outText("+++ setting group <".. conf.theGroup:getName() .. "> jtac command", 30)
if jtacGrpUI.simpleCommands then if jtacGrpUI.simpleCommands then
-- we install directly in F-10 other -- we install directly in F-10 other
if not conf.myMainMenu then if not conf.myMainMenu then
local commandTxt = "jtac Lasing Report" local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id, commandTxt, nil, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
commandTxt,
nil,
jtacGrpUI.redirectCommandX,
{conf, "lasing report"}
)
conf.myMainMenu = theCommand conf.myMainMenu = theCommand
end end
return return
end end
-- ok, first, if we don't have an F-10 menu, create one -- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac') conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac')
@ -178,12 +183,6 @@ function jtacGrpUI.setCommsMenu(theGroup)
end end
function jtacGrpUI.addSubMenus(conf) 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 commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
@ -193,24 +192,8 @@ function jtacGrpUI.addSubMenus(conf)
{conf, "lasing report"} {conf, "lasing report"}
) )
table.insert(conf.myCommands, theCommand) 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 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) function jtacGrpUI.redirectCommandX(args)
timer.scheduleFunction(jtacGrpUI.doCommandX, args, timer.getTime() + 0.1) timer.scheduleFunction(jtacGrpUI.doCommandX, args, timer.getTime() + 0.1)
end end
@ -219,25 +202,25 @@ function jtacGrpUI.doCommandX(args)
local conf = args[1] -- < conf in here local conf = args[1] -- < conf in here
local what = args[2] -- < second argument in here local what = args[2] -- < second argument in here
local theGroup = conf.theGroup local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
local targetList = jtacGrpUI.collectJTACtargets(conf, true) local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list -- iterate the list
if #targetList < 1 then if #targetList < 1 then
trigger.action.outTextForGroup(conf.id, "No targets are currently being lased", 30) trigger.action.outTextForGroup(conf.id, "No targets are currently being lased", 30)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
return return
end end
local desc = "JTAC Target Report:\n" local desc = "JTAC Target Report:\n"
-- trigger.action.outTextForGroup(conf.id, "Target Report:", 30)
for i=1, #targetList do for i=1, #targetList do
local aTarget = targetList[i] local aTarget = targetList[i]
if aTarget.idle then if aTarget.idle then
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target" desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target"
else 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
end end
trigger.action.outTextForGroup(conf.id, desc .. "\n", 30) trigger.action.outTextForGroup(conf.id, desc .. "\n", 30)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
end end
function jtacGrpUI.collectJTACtargets(conf, includeIdle) function jtacGrpUI.collectJTACtargets(conf, includeIdle)
@ -249,14 +232,14 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
local theJTACS = {} local theJTACS = {}
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
if troop.coalition == conf.coalition if troop.coalition == conf.coalition
and troop.orders == "laze" and troop.orders == "laze"
and troop.lazeTarget and troop.lazeTarget
and troop.lazeTarget:isExist() and troop.lazeTarget:isExist()
then then
table.insert(theJTACS, troop) table.insert(theJTACS, troop)
elseif troop.coalition == conf.coalition elseif troop.coalition == conf.coalition
and troop.orders == "laze" and troop.orders == "laze"
and includeIdle and includeIdle
then then
-- we also include idlers -- we also include idlers
table.insert(theJTACS, troop) table.insert(theJTACS, troop)
@ -277,7 +260,7 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
local jtacLoc = dcsCommon.getGroupLocation(troop.group) local jtacLoc = dcsCommon.getGroupLocation(troop.group)
local nearestZone = cfxOwnedZones.getNearestOwnedZoneToPoint(jtacLoc) local nearestZone = cfxOwnedZones.getNearestOwnedZoneToPoint(jtacLoc)
if nearestZone then 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 ozRange = math.floor(ozRange * 10) / 10
local relPos = dcsCommon.compassPositionOfARelativeToB(jtacLoc, nearestZone.point) local relPos = dcsCommon.compassPositionOfARelativeToB(jtacLoc, nearestZone.point)
aTarget.posInfo = " (" .. ozRange .. "nm " .. relPos .. " of " .. nearestZone.name .. ")" 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 = aTarget.range * 0.000621371 -- meter to miles
aTarget.range = math.floor(aTarget.range * 10) / 10 aTarget.range = math.floor(aTarget.range * 10) / 10
aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there) aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there)
--aTarget.jtacName = troop.name
aTarget.lazeTargetType = troop.lazeTargetType aTarget.lazeTargetType = troop.lazeTargetType
end end
table.insert(targetList, aTarget) table.insert(targetList, aTarget)
@ -309,87 +291,82 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
end 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 function jtacGrpUI:onEvent(theEvent)
-- receive a comms menu and that they receive a clean-up if not theEvent then return end
-- when required local theUnit = theEvent.initiator
-- if not theUnit then return end
-- Callbacks are provided by cfxPlayer module to which we local uName = theUnit:getName()
-- subscribe during init if not theUnit.getPlayerName then return end
-- if not theUnit:getPlayerName() then return end
function jtacGrpUI.playerChangeEvent(evType, description, player, data) -- we now have a player birth event.
--trigger.action.outText("+++ groupUI: received <".. evType .. "> Event", 30) local pName = theUnit:getPlayerName()
if evType == "newGroup" then local theGroup = theUnit:getGroup()
-- initialized attributes are in data as follows if not theGroup then return end
-- .group - new group local gName = theGroup:getName()
-- .name - new group's name if not gName then return end
-- .primeUnit - the unit that trigggered new group appearing if jtacGrpUI.verbose then
-- .primeUnitName - name of prime unit trigger.action.outText("+++jGUI: birth player. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30)
-- .id group ID end
--theUnit = data.primeUnit local conf = jtacGrpUI.getConfigByGroupName(gName)
jtacGrpUI.setCommsMenu(data.group) if conf then
-- trigger.action.outText("+++ groupUI: added " .. theUnit:getName() .. " to comms menu", 30) jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
return jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
end 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
jtacGrpUI.setCommsMenu(theGroup)
end end
-- --
-- Start -- 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 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 -- GO GO GO
-- if not jtacGrpUI.start() then
if not cfxGroundTroops then trigger.action.outText("JTAC GUI failed to start up.", 30)
trigger.action.outText("cf/x jtacGrpUI REQUIRES cfxGroundTroops to work.", 30) jtacGrpUI = nil
else end
jtacGrpUI.start()
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 = {}
unGrief.version = "1.2.0" unGrief.version = "2.0.0"
unGrief.verbose = false unGrief.verbose = false
unGrief.ups = 1 unGrief.ups = 1
unGrief.requiredLibs = { unGrief.requiredLibs = {
@ -21,6 +21,11 @@ unGrief.disabledFlagValue = unGrief.enabledFlagValue + 100 -- DO NOT CHANGE
- strict rules - strict rules
- warnings on enter/exit - warnings on enter/exit
- warnings optional - 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) trigger.action.outText("+++uGrf: <" .. theZone.name .. "> is designated as PVP legal", 30)
end end
theZone.strictPVP = cfxZones.getBoolFromZoneProperty(theZone, "strict", false) theZone.strictPVP = theZone:getBoolFromZoneProperty("strict", false)
end end
-- vengeance: if player killed before, they are no longer welcome -- 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) 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 if not theEvent.initiator then return end
local theUnit = theEvent.initiator local theUnit = theEvent.initiator
if not theUnit.getPlayerName then return end -- wierd stuff happening here if not theUnit.getPlayerName then return end -- wierd stuff happening here
@ -94,6 +106,7 @@ function unGrief.exactVengance(theEvent)
-- tell ssb to kick now: -- tell ssb to kick now:
trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue) trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue)
trigger.action.outText("Player <" .. playerName .. "> is not welcome here. Shoo! Shoo!", 30) trigger.action.outText("Player <" .. playerName .. "> is not welcome here. Shoo! Shoo!", 30)
timer.scheduleFunction(unGrief.reconcile, groupName, timer.getTime() + 15)
return return
end end
@ -200,6 +213,7 @@ function unGrief:onEvent(theEvent)
local groupName = theGroup:getName() local groupName = theGroup:getName()
-- tell ssb to kick now: -- tell ssb to kick now:
trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue) trigger.action.setUserFlag(groupName, unGrief.disabledFlagValue)
timer.scheduleFunction(unGrief.reconcile, groupName, timer.getTime() + 15)
return return
end end
-- aaand all your base are belong to us! -- aaand all your base are belong to us!
@ -256,23 +270,27 @@ function unGrief.readConfigZone()
theZone = cfxZone.createSimpleZone("unGriefConfig") theZone = cfxZone.createSimpleZone("unGriefConfig")
end end
unGrief.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) unGrief.verbose = theZone.verbose
unGrief.graceKills = cfxZones.getNumberFromZoneProperty(theZone, "graceKills", 1) unGrief.graceKills = theZone:getNumberFromZoneProperty("graceKills", 1)
unGrief.retaliation = cfxZones.getStringFromZoneProperty(theZone, "retaliation", "boom") -- other possible methods: ssb unGrief.retaliation = theZone:getStringFromZoneProperty("retaliation", "boom") -- other possible methods: ssb
unGrief.retaliation = dcsCommon.trim(unGrief.retaliation:lower()) unGrief.retaliation = dcsCommon.trim(unGrief.retaliation:lower())
if unGrief.retaliation == "ssb" then
-- now turn on ssb
unGrief.wrathful = cfxZones.getBoolFromZoneProperty(theZone, "wrathful", false) trigger.action.setUserFlag("SSB",100)
trigger.action.outText("unGrief: SSB enabled for retaliation.", 30)
unGrief.pve = cfxZones.getBoolFromZoneProperty(theZone, "pve", false)
if cfxZones.hasProperty(theZone, "pveOnly") then
unGrief.pve = cfxZones.getBoolFromZoneProperty(theZone, "pveOnly", false)
end 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 if unGrief.verbose then
trigger.action.outText("+++uGrf: read config", 30) trigger.action.outText("+++uGrf: read config", 30)