Version 2.3.0

DCS Jul-11, Jul-22, Aug-09 code hardening.
NEW: Convoy and TWN
This commit is contained in:
Christian Franz 2024-08-15 11:36:45 +02:00
parent 76083ff2b6
commit f7a8705aa5
37 changed files with 1972 additions and 698 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "2.1.1"
FARPZones.version = "2.2.0"
FARPZones.verbose = false
--[[--
Version History
@ -24,7 +24,8 @@ FARPZones.verbose = false
2.0.2 - clean-up
verbosity enhancements
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded
--]]--
@ -331,9 +332,7 @@ end
function FARPZones.produceVehicles(theFarp)
local theZone = theFarp.zone
-- trigger.action.outText("entering veh prod run for farp zone <" .. theZone.name .. ">, owner is <" .. theFarp.owner .. ">", 30)
--end
-- abort production if farp is owned by neutral and
-- neutralproduction is false
if theFarp.owner == 0 and not theFarp.neutralProduction then
@ -382,18 +381,7 @@ function FARPZones.produceVehicles(theFarp)
-- spawn resource vehicles
FARPZones.produceResourceVehicles(theFarp, theCoalition)
--[[--
unitTypes = FARPZones.resourceTypes
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone,
unitTypes,
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
theFarp.resourceData = theData
--]]--
-- update unique counter
theFarp.count = theFarp.count + 1
end
@ -508,7 +496,6 @@ function FARPZones.saveData()
-- iterate all farp data and put them into a container each
for theZone, theFARP in pairs(FARPZones.allFARPZones) do
fName = theZone.name
--trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
local fData = {}
fData.owner = theFARP.owner
fData.defenderData = dcsCommon.clone(theFARP.defenderData)
@ -528,6 +515,17 @@ function FARPZones.saveData()
return theData
end
function FARPZones.delayedSSB()
-- invoke SSBClient to re-scan all airfields
-- if it is loaded in the mission
if FARPZones.verbose then
trigger.action.outText("FARPz: delayed SSB invocation", 30)
end
if cfxSSBClient then
cfxSSBClient.setSlotAccessByAirfieldOwner()
end
end
function FARPZones.loadMission()
local theData = persistence.getSavedDataForModule("FARPZones")
if not theData then
@ -548,27 +546,9 @@ function FARPZones.loadMission()
theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
--[[--
local groupData = fData.defenderData
if groupData and #groupData.units > 0 then
local cty = groupData.cty
local cat = groupData.cat
theFARP.defenders = coalition.addGroup(cty, cat, groupData)
end
groupData = fData.resourceData
if groupData and #groupData.units > 0 then
local cty = groupData.cty
local cat = groupData.cat
theFARP.resources = coalition.addGroup(cty, cat, groupData)
end
--]]--
FARPZones.produceVehicles(theFARP) -- do full defender and resource cycle
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map
-- if (not theFARP.defenders) and (not theFARP.resources) then
-- we instigate a resource and defender drop
-- FARPZones.produceVehicles(theFARP)
-- end
else
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end
@ -580,10 +560,8 @@ end
-- Start
--
function FARPZones.releaseFARPS()
-- trigger.action.outText("Releasing hold on FARPS", 30)
for idx, aFarp in pairs(FARPZones.lockup) do
aFarp:autoCapture(true)
-- trigger.action.outText("releasing farp <" .. aFarp:getName() .. ">", 30)
end
end
@ -592,13 +570,10 @@ function FARPZones.readConfig()
if not theZone then
theZone = cfxZones.createSimpleZone("farpZonesConfig")
end
FARPZones.verbose = theZone.verbose
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1)
end
@ -635,8 +610,6 @@ function FARPZones.start()
for k, aZone in pairs(theZones) do
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
FARPZones.addFARPZone(aFARP) -- add to managed zones
-- moved FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
-- moved FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
if FARPZones.verbose then
trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30)
end
@ -656,6 +629,7 @@ function FARPZones.start()
FARPZones.startingUp = false -- not needed / read anywhere
timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5)
timer.scheduleFunction(FARPZones.delayedSSB, {}, timer.getTime() + 10)
if FARPZones.refresh > 0 then
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)

View File

@ -75,13 +75,9 @@ function rndFlags.createRNDWithZone(theZone)
-- trigger flag
if theZone:hasProperty("f?") then
theZone.triggerFlag = theZone:getStringFromZoneProperty("f?", "none")
end
if theZone:hasProperty("in?") then
elseif theZone:hasProperty("in?") then
theZone.triggerFlag = theZone:getStringFromZoneProperty("in?", "none")
end
if theZone:hasProperty("rndPoll?") then
elseif theZone:hasProperty("rndPoll?") then
theZone.triggerFlag = theZone:getStringFromZoneProperty("rndPoll?", "none")
end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {}
cfxSSBClient.version = "4.0.1"
cfxSSBClient.version = "5.0.0"
cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -8,7 +8,7 @@ cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
cfxSSBClient.requiredLibs = {
"dcsCommon", -- always
"cfxMX", --"cfxGroups", -- for slot access
"cfxMX", -- for ME Player data access
"cfxZones", -- Zones, of course
}
@ -17,7 +17,13 @@ Version History
4.0.0 - dmlZones
- cfxMX instead of cfxGroups
4.0.1 - check slot availability immediately upon start
- ssb autoenable option
- ssb auto-enable option
5.0.0 - re-write: support for DCS new dynamic spawns
- work-around for DCS bug that passes dead / deallocated objects
- SINGLE-USE and dynamic spawns are mutually exclusive
as single-use cannot cover dynamic slots, so authors must
disable or limit the amount of planes manually
--]]--
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
@ -36,9 +42,9 @@ cfxSSBClient.slotActions = {
cfxSSBClient.keepInAirGroups = false -- if false we only look at planes starting on the ground
-- setting this to true only makes sense if you plan to bind in-air starts to airfields
cfxSSBClient.playerGroups = {}
cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying
cfxSSBClient.playerGroups = {} -- indexed by groupName. group data with .airfield attribute
cfxSSBClient.closedAirfields = {} -- list that closes airfields for all aircrafts
cfxSSBClient.playerPlanes = {} -- names of unit that a player is flying indexed by unit name
cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane
cfxSSBClient.slotState = {} -- keeps a record of which slot has which value. For persistence and debugging
cfxSSBClient.occupiedUnits = {} -- by unit name if occupied to prevent kicking. clears after crash or leaving plane
@ -154,6 +160,7 @@ end
function cfxSSBClient.setSlotAccessForGroup(theGroup)
if not theGroup then return end
-- WARNING: theGroup is cfxGroup record
-- amended for dynamic groups
local theName = theGroup.name
-- we now check if any plane of that group is still
@ -163,15 +170,27 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
-- we now iterate all playerUnits in theGroup.
-- theGroup is cfxGroup
for idx, playerData in pairs (theGroup.playerUnits) do
local uName = playerData.name
if cfxSSBClient.occupiedUnits[uName] then
if theGroup.playerUnits then
-- this is a ME unit
for idx, playerData in pairs (theGroup.playerUnits) do
local uName = playerData.name
if cfxSSBClient.occupiedUnits[uName] then
if cfxSSBClient.verbose then
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
end
return
end
end
else
-- this is a dynamic unit
if cfxSSBClient.occupiedUnits[theGroup.uName] then
local uName = theGroup.uName
if cfxSSBClient.verbose then
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
trigger.action.outText("+++ssbc: DYNAMIC unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
end
return
end
end
end
-- when we get here, no unit in the entire group is occupied
local theMatchingAirfield = theGroup.airfield
@ -230,8 +249,6 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end
trigger.action.setUserFlag(theName, blockState)
cfxSSBClient.slotState[theName] = blockState
--if cfxSSBClient.verbose then
--end
else
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30)
@ -252,18 +269,22 @@ function cfxSSBClient.setSlotAccessForUnit(theUnit) -- calls setSlotAccessForGro
end
function cfxSSBClient.getPlayerGroupForGroupNamed(aName)
--is now indexed !!
return cfxSSBClient.playerGroups[aName]
--[[--
local pGroups = cfxSSBClient.playerGroups
for idx, theGroup in pairs(pGroups) do
if theGroup.name == aName then return theGroup end
end
return nil
return nil
--]]--
end
function cfxSSBClient.setSlotAccessByAirfieldOwner()
-- get all groups that have a player-controlled aircraft
-- now uses cached, reduced set of player planes
local pGroups = cfxSSBClient.playerGroups
for idx, theGroup in pairs(pGroups) do
local pGroups = cfxSSBClient.playerGroups -- indexed by name
for gName, theGroup in pairs(pGroups) do
cfxSSBClient.setSlotAccessForGroup(theGroup)
end
end
@ -294,14 +315,8 @@ function cfxSSBClient:onEvent(event)
end
return
end
local curH = theUnit:getLife()
local maxH = theUnit:getLife0()
if not theUnit.getName then return end -- WTF???
local uName = theUnit:getName()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: Player leaves unit <" .. uName .. ">", 30)
trigger.action.outText("+++SSB: unit health check: " .. curH .. " of " .. maxH, 30)
end
cfxSSBClient.occupiedUnits[uName] = nil -- forget I was occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
return
@ -317,9 +332,10 @@ function cfxSSBClient:onEvent(event)
end
-- write down player names and planes
if event.id == 15 then -- birth
if event.id == 15 then -- birth / spawn
if not event.initiator then return end
local theUnit = event.initiator -- we know this exists
if not theUnit.getName then return end -- hardening
local uName = theUnit:getName()
if not uName then return end
-- player entered unit?
@ -336,6 +352,12 @@ function cfxSSBClient:onEvent(event)
end
-- remember this unit as player controlled plane
-- because player and plane can easily disconnect
-- find out if this is a dynamic spawn
local isDynamic = cfxMX.isDynamicPlayer(theUnit)
if isDynamic then
trigger.action.outText("+++SSBC: detected dynamic player spawn for unit <" .. uName .. ">, id <" .. theUnit:getID() .. ">, group <" .. theUnit:getGroup():getName() .. ">, player <" .. playerName .. ">", 30)
cfxSSBClient.amendPlayerData(theUnit) -- get airport and add to managed slots
end
cfxSSBClient.playerPlanes[uName] = playerName
if cfxSSBClient.verbose then
trigger.action.outText("+++SSBC:SU: noted " .. playerName .. " piloting player unit " .. uName, 30)
@ -349,16 +371,21 @@ function cfxSSBClient:onEvent(event)
if event.id == 5 then -- crash PRE-processing
if not event.initiator then return end
local theUnit = event.initiator
if not theUnit.getName then return end
local uName = theUnit:getName()
cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
-- DO NOT RETURN NOW!!! singleuse proccing follows
end
if cfxSSBClient.singleUse and event.id == 5 then -- crash
--if not event.initiator then return end
local theUnit = event.initiator
if not theUnit then return end
if not theUnit.getName then return end
local uName = theUnit:getName()
if not uName then return end
if not theUnit.getGroup then return end
local theGroup = theUnit:getGroup()
if not theGroup then return end
-- see if a player plane
@ -371,6 +398,7 @@ function cfxSSBClient:onEvent(event)
return
end
-- if we get here, a player-owned plane has crashed
if not theGroup.getName then return end -- better safe than sorry
local gName = theGroup:getName()
if not gName then return end
@ -435,27 +463,34 @@ end
-- pre-process static player data to minimize
-- processor load on checks
function cfxSSBClient.processPlayerData()
cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
local pGroups = cfxSSBClient.playerGroups
function cfxSSBClient.processSSBPlayerData()
--cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
local pGroups = cfxSSBClient.SSBPlayerData -- cfxSSBClient.playerGroups
local filteredPlayers = {}
for idx, theGroup in pairs(pGroups) do
for gName, theGroup in pairs(pGroups) do
if theGroup.airfield ~= nil or cfxSSBClient.keepInAirGroups or
cfxSSBClient.singleUse then
-- only transfer groups that have airfields (or also keepInAirGroups or when single-use)
-- attached. Ignore the rest as they are
-- always fine
table.insert(filteredPlayers, theGroup)
--table.insert(filteredPlayers, theGroup)
filteredPlayers[gName] = theGroup
end
end
cfxSSBClient.playerGroups = filteredPlayers
-- we can now relinquish SSBPlayerData
cfxSSBClient.SSBPlayerData = nil
end
-- add airfield information to each player group
-- WARNING: AMENDS/MODIFIES DATA IN MX TO CONTAIN AIRFIELDS
-- now changed to internal clones
function cfxSSBClient.processGroupData()
local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute
local processed = {}
for idx, theGroup in pairs(pGroups) do
-- we always use the first player's plane as referenced
local cGroup = dcsCommon.clone(theGroup)
local playerData = theGroup.playerUnits[1]
local theAirfield = nil
local delta = -1
@ -471,20 +506,52 @@ function cfxSSBClient.processGroupData()
end
if delta > cfxSSBClient.maxAirfieldRange then
-- forget airfield
theAirfield = nil
theAirfield = nil
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " unlinked - too far from airfield" , 30)
end
-- end
else
cGroup.airfield = theAirfield -- we update the clone
-- add to my player groups, indexed by group name
processed[theGroup.name] = cGroup -- we keep the clone
end
theGroup.airfield = theAirfield
else
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " start option " .. action .. " does not concern SSB", 30)
end
end
end
cfxSSBClient.SSBPlayerData = processed -- remember all relevant clones for post-processing
end
function cfxSSBClient.amendPlayerData(theUnit)
-- enter with single, dynamically spawning unit and add an
-- entry for cfxSSBClient.SSBPlayerData to allow entry for
-- group's airbase / FARP
local dynGroup = theUnit:getGroup()
local theGroup = {} -- entry into db
theGroup.name = dynGroup:getName()
local thePoint = theUnit:getPoint()
local theAirfield, delta = cfxSSBClient.getClosestAirbaseTo(thePoint)
local afName = theAirfield:getName()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " closest to AF " .. afName .. ": " .. math.floor(delta) .. "m" , 30)
end
if delta > cfxSSBClient.maxAirfieldRange then
-- forget airfield
--theAirfield = nil
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " unlinked - too far from airfield (???)" , 30 )
end
else
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " added to SSBPlayerData for slot management" , 30)
end
theGroup.uName = theUnit:getName()
cfxSSBClient.playerGroups[theGroup.name] = theGroup
end
end
--
-- read config zone
--
@ -589,7 +656,7 @@ function cfxSSBClient.start()
-- process player data to minimize effort and build cache
-- into cfxSSBClient.playerGroups
cfxSSBClient.processPlayerData()
cfxSSBClient.processSSBPlayerData() -- processPlayerData()
-- process ssbc zones
-- for in-mission DML interface

View File

@ -1,5 +1,5 @@
tdz = {}
tdz.version = "1.0.3"
tdz.version = "1.1.0"
tdz.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -18,6 +18,7 @@ VERSION HISTORY
1.0.2 - manual placement option
filters FARPs
1.0.3 - "manual" now defaults to false
1.1.0 - now supports event 55 (runway touch)
--]]--
@ -326,7 +327,7 @@ function tdz:onEvent(event)
if not theUnit.getPlayerName then return end
local playerName = theUnit:getPlayerName()
if not playerName then return end
if event.id == 4 then
if event.id == 4 or event.id == 55 then
-- player landed
tdz.playerLanded(theUnit, playerName)
end

View File

@ -1,5 +1,5 @@
airfield = {}
airfield.version = "2.1.1"
airfield.version = "2.2.0"
airfield.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -21,7 +21,7 @@ airfield.allAirfields = {} -- inexed by af name, db entries: base, cat
-- support for FARPS as well
2.1.0 - added support for makeNeutral?
2.1.1 - bug fixing for DCS 2.9x airfield retrofit
2.2.0 - dmlZone:getCoalition() / masterowner adaptation for owner
--]]--
-- init all airfields DB
@ -109,11 +109,6 @@ function airfield.createAirFieldFromZone(theZone)
airfield.assumeControl(theZone)
end
if theZone:hasProperty("ownedBy#") then
theZone.ownedBy = theZone:getStringFromZoneProperty("ownedBy#", "<none>")
trigger.action.setUserFlag(theZone.ownedBy, theZone.owner)
end
-- if fixed attribute, we switch to that color and keep it fixed.
-- can be overridden by either makeXX or autoCap.
if theZone:hasProperty("fixed") then
@ -124,6 +119,11 @@ function airfield.createAirFieldFromZone(theZone)
theZone.owner = theFixed
end
if theZone:hasProperty("ownedBy#") then
theZone.ownedBy = theZone:getStringFromZoneProperty("ownedBy#", "<none>")
trigger.action.setUserFlag(theZone.ownedBy, theZone.owner)
end
-- index by name, and warn if duplicate associate
if airfield.myAirfields[theZone.afName] then
trigger.action.outText("+++airF: WARNING - zone <" .. theZone.name .. "> redefines airfield <" .. theZone.afName .. ">, discarded!", 30)
@ -182,7 +182,7 @@ function airfield.showAirfield(theZone)
local lineColor = theZone.redLine -- {1.0, 0, 0, 1.0} -- red
local fillColor = theZone.redFill -- {1.0, 0, 0, 0.2} -- red
local owner = theZone.owner
local owner = theZone:getCoalition() -- .owner
if owner == 2 then
lineColor = theZone.blueLine -- {0.0, 0, 1.0, 1.0}
fillColor = theZone.blueFill -- {0.0, 0, 1.0, 0.2}
@ -315,6 +315,7 @@ function airfield.update()
if theZone.owner ~= 1 then -- only send cap event when capped
airfield.airfieldCaptured(theAirfield)
end
theZone.owner = 1
end
if theZone.makeBlue and theZone:testZoneFlag(theZone.makeBlue, theZone.triggerMethod, "lastMakeBlue") then
@ -329,6 +330,7 @@ function airfield.update()
if theZone.owner ~= 2 then -- only send cap event when capped
airfield.airfieldCaptured(theAirfield)
end
theZone.owner = 2
end
if theZone.makeNeutral and theZone:testZoneFlag(theZone.makeNeutral, theZone.triggerMethod, "lastMakeNeutral") then
@ -343,6 +345,7 @@ function airfield.update()
if theZone.owner ~= 0 then -- only send cap event when capped
airfield.airfieldCaptured(theAirfield) -- 0 cap will not cause any signals, but we do this anyway
end
theZone.owner = 0
end

View File

@ -1,5 +1,5 @@
bombRange = {}
bombRange.version = "2.0.0"
bombRange.version = "2.0.2"
bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = {
@ -13,7 +13,7 @@ VERSION HISTORY
*after* impact on high-resolution scans (30fps)
set resolution to 30 ups by default
order of events: check kills against dropping projectiles
collecd dead, and compare against missing erdnance while they are fresh
collect dead, and compare against missing erdnance while they are fresh
GC
interpolate hits on dead when looking at kills and projectile does
not exist
@ -26,6 +26,9 @@ VERSION HISTORY
2.0.0 - support for radioMainMenu
- support for types
- types can have wild cards
2.0.1 - says hi! on start
- fixes for DCS Jul 11 bugs
2.0.2 - fixes for DCS Jul 22 bugs
--]]--
@ -197,7 +200,7 @@ function bombRange.initCommsForUnit(theUnit)
if bombRange.mainMenu then
mainMenu = radioMenu.getMainMenuFor(bombRange.mainMenu) -- nilling both next params will return menus[0]
end
if not theUnit.getName then return end -- Jul-22 DCS bug
local uName = theUnit:getName()
local pName = theUnit:getPlayerName()
local theGroup = theUnit:getGroup()
@ -274,6 +277,7 @@ end
-- Event Proccing
--
function bombRange.suspectedHit(weapon, target)
if not Object.isExist(weapon) then return end -- DCS july 11 2024 issue
local wType = weapon:getTypeName()
if not target then return end
if target:getCategory() == 5 then -- scenery
@ -407,15 +411,19 @@ end
function bombRange:onEvent(event)
if not event.initiator then return end
local theUnit = event.initiator
if not Unit.isExist(theUnit) then return end -- DCS issue Jul-11
if not theUnit.getName then return end -- DCS issue Jul-22
if event.id == 2 then -- hit: weapon still exists
if not event.weapon then return end
if not Object.isExist(event.weapon) then return end -- Jul-11 issue
bombRange.suspectedHit(event.weapon, event.target)
return
end
if event.id == 28 then -- kill: similar to hit, but due to new mechanics not reliable
if not event.weapon then return end
if not Object.isExist(event.weapon) then return end -- Jul-11 issue
bombRange.suspectedHit(event.weapon, event.target)
return
end
@ -425,6 +433,7 @@ function bombRange:onEvent(event)
-- these events can come *before* weapon disappears
local killDat = {}
killDat.victim = event.initiator
if not Object.isExist(event.initiator) then return end -- Jul-11 issue
killDat.p = event.initiator:getPoint()
killDat.when = timer.getTime()
killDat.name = dcsCommon.uuid("vic")
@ -454,7 +463,8 @@ function bombRange:onEvent(event)
end
local w = event.weapon
local b = {}
local bName = w:getName()
local bName = "unknown"
if w.getName then bName = w:getName() end -- Jul-22 DCS bug
b.name = bName
b.type = w:getTypeName()
-- may need to verify type: how do we handle clusters or flares?
@ -793,6 +803,8 @@ function bombRange.start()
-- start GC
bombRange.GC()
-- say hi!
trigger.action.outText("cf/x Bomb Range v" .. bombRange.version .. " started.", 30)
return true
end

View File

@ -1,6 +1,6 @@
camp = {}
camp.ups = 1
camp.version = "1.0.2"
camp.version = "1.1.0"
camp.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -18,6 +18,7 @@ VERSION HISTORY
- actionSound
- output sound with communications
1.0.2 - integration with FARPZones
1.1.0 - support for DCS 2.9.6 dynamic spawns
--]]--
--
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
@ -110,24 +111,55 @@ function camp.update()
end
function camp:onEvent(theEvent)
if not theEvent then return end
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
-- if not theUnit.getName then return end
-- if not theUnit.getPlayerName then return end
if not cfxMX.isDynamicPlayer(theUnit) then return end
local id = theEvent.id
if id == 15 then -- birth
camp.lateProcessPlayer(theUnit)
if camp.verbose then
trigger.action.outText("camp: late player processing for <" .. theUnit:getName() .. ">", 30)
end
end
end
--
-- Comms
--
function camp.lateProcessPlayer(theUnit)
if not theUnit then return end
if not theUnit.getGroup then return end
local theGroup = theUnit:getGroup()
local gName = theGroup:getName()
local gID = theGroup:getID()
camp.installComsFor(gID, gName)
end
function camp.installComsFor(gID, gName)
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"})
end
function camp.processPlayers()
-- install coms stump for all players. they will be switched in/out
-- whenever it is apropriate
for idx, gData in pairs(cfxMX.playerGroupByName) do
gID = gData.groupId
gName = gData.name
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
--[[-- local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"})
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"}) --]]--
camp.installComsFor(gID, gName)
end
end

View File

@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "2.0.2"
cfxMX.version = "2.1.0"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@ -13,7 +13,12 @@ cfxMX.verbose = false
- harmonized with cfxGroups
2.0.1 - groupHotByName
2.0.2 - partOfGroupDataInZone(), allGroupsInZoneByData() from milHelo
2.0.3 - allGroupsInZoneByData supports type filtering
2.1.0 - support for dynamically spawning player unit detection
- new isDynamicPlayer()
- new isMEPlayer()
- new isMEPlayerGroup()
--]]--
cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
@ -37,16 +42,14 @@ cfxMX.playerUnit2Group = {} -- returns a group data for player units.
cfxMX.groups = {} -- all groups indexed b yname, cfxGroups folded into cfxMX
--[[-- group objects are
{
name= "",
coalition = "" (red, blue, neutral),
coanum = # (0, 1, 2 for neutral, red, blue)
category = "" (helicopter, ship, plane, vehicle, static),
hasPlayer = true/false,
playerUnits = {} (for each player unit in group: name, point, action)
}
{
name= "",
coalition = "" (red, blue, neutral),
coanum = # (0, 1, 2 for neutral, red, blue)
category = "" (helicopter, ship, plane, vehicle, static),
hasPlayer = true/false,
playerUnits = {} (for each player unit in group: name, point, action)
}
--]]--
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end
@ -359,11 +362,14 @@ function cfxMX.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
return false
end
function cfxMX.allGroupsInZoneByData(theZone) -- returns groups indexed by name and count
function cfxMX.allGroupsInZoneByData(theZone, cat) -- returns groups indexed by name and count
if not cat then cat = {"helicopter", "ship", "plane", "vehicle" } end
if type(cat) == "string" then cat = {cat} end
local theGroupsInZone = {}
local count = 0
for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then
local gType = cfxMX.groupTypeByName[groupName]
if dcsCommon.arrayContainsString(cat, gType) and groupData.units then
if cfxMX.partOfGroupDataInZone(theZone, groupData.units) then
theGroupsInZone[groupName] = groupData -- DATA! work on clones!
count = count + 1
@ -375,7 +381,36 @@ function cfxMX.allGroupsInZoneByData(theZone) -- returns groups indexed by name
end
return theGroupsInZone, count
end
function cfxMX.isDynamicPlayer(theUnit)
if not theUnit then return false end
if not theUnit.getName then return false end
if not theUnit.getPlayerName then return false end
if not theUnit:getPlayerName() then return false end
local uName = theUnit:getName()
if cfxMX.playerUnitByName[uName] then return false end
return true
end
function cfxMX.isMEPlayer(theUnit)
if not theUnit then return false end
if not theUnit.getName then return false end
if not theUnit.getPlayerName then return false end
if not theUnit:getPlayerName() then return false end
local uName = theUnit:getName()
if cfxMX.playerUnitByName[uName] then return true end
return false
end
function cfxMX.isMEPlayerGroup(theUnit)
if not theUnit then return false end
if not theUnit.getName then return end
if not theUnit.getPlayerName then return end
local uName = theUnit:getName()
if cfxMX.playerUnitByName[uName] then return true end
return false
end
function cfxMX.start()
cfxMX.createCrossReferences()
if cfxMX.verbose then

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.3.6"
cfxZones.version = "4.4.2"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -9,38 +9,6 @@ cfxZones.version = "4.3.6"
--
--[[-- VERSION HISTORY
- 4.0.0 - dmlZone OOP API started
- code revision / refactoring
- moved createPoint and copxPoint to dcsCommon, added bridging code
- re-routed all createPoint() invocations to dcsCommon
- removed anyPlayerInZone() because of cfxPlayer dependency
- numberArrayFromString() moved to dcsCommon, bridged
- flagArrayFromString() moved to dcsCommon, bridged
- doPollFlag() can differentiate between number method and string method
to enable passing an immediate negative value
- getNumberFromZoneProperty() enforces number return even on default
- immediate method switched to preceeding '#', to resolve conflict witzh
negative numbers, backwards compatibility with old (dysfunctional) method
- 4.0.1 - dmlZone:getName()
- 4.0.2 - removed verbosity from declutterZone (both versions)
- 4.0.3 - new processDynamicVZU()
- wildcard uses processDynamicVZU
- 4.0.4 - setFlagValue now supports multiple flags (OOP and classic)
- doSetFlagValue optimizations
- 4.0.5 - dynamicAB wildcard
- processDynamicValueVU
- 4.0.6 - hash mark forgotten QoL
- 4.0.7 - drawZone()
- 4.0.8 - markZoneWithObjects()
- cleanup
- markCenterWithObject
- markPointWithObject
- 4.0.9 - createPolyZone now correctly returns new zone
- createSimplePolyZone correctly passes location to createPolyZone
- createPolyZone now correctly sets zone.point
- createPolyZone now correctly inits dcsOrigin
- createCircleZone noew correctly inits dcsOrigin
- 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false)
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates
- 4.1.2 - hash property missing warning
@ -49,7 +17,7 @@ cfxZones.version = "4.3.6"
- small optimization for randomInRange()
- randomDelayFromPositiveRange also allows 0
- 4.3.1 - new drawText() for zones
- dmlZones:getClosestZone() bridge
- dmlZone:getClosestZone() bridge
- 4.3.2 - new getListFromZoneProperty()
- 4.3.3 - hardened calculateZoneBounds
- 4.3.4 - rewrote zone bounds for poly zones
@ -57,7 +25,13 @@ cfxZones.version = "4.3.6"
- 4.3.6 - tiny optimization in isPointInsideQuad
- moving zone - hardening code for static objects
- moving zones - now deriving dx, dy,uHeading from dcsCommon xref for linked zones
- 4.3.7 - corrected bug in processDynamicValues for lookup table
- 4.4.0 - dmlZone:getCoalition()
- dmlZone:getTypeName()
- dmlZone supports masterOwner by default
- dmlZone:getCoalition() dereferences masterOwner once
-4.4.1 - better verbosity for error in doPollFlag()
-4.4.2 - twn support for wildcards <twn: > and <loc:>
--]]--
--
@ -81,6 +55,13 @@ function dmlZone:new(o)
return o
end
-- dmlZone compatibility with DCS MSE objects:
-- dmlZone:getName() -- returns zone.name attribute (from ME)
-- dmlZone:getPoint() -- returns current point or dmlPoint
-- dmlZone:getTypeName() -- returns "dmlZone"
-- dmlZone:getCoalition -- returns owner
--
-- CLASSIC INTERFACE
--
@ -1615,7 +1596,7 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
else
if method ~= "on" and method ~= "f=1" then
trigger.action.outText("+++zones: unknown method <" .. method .. "> - using 'on'", 30)
trigger.action.outText("+++zones: unknown method <" .. method .. "> for flag <" .. theFlag .. "> in zone <" .. theZone.name .. "> - setting to 1", 30)
end
-- default: on.
-- trigger.action.setUserFlag(theFlag, 1)
@ -2922,7 +2903,7 @@ function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
if not val or (val < 1) then val = 1 end
if val > msgResponses then val = msgResponses end
if val > #msgResponses then val = #msgResponses end
val = msgResponses[val]
val = dcsCommon.trim(val)
@ -2968,7 +2949,7 @@ end
-- process <lat/lon/ele/mgrs/lle/latlon/alt/vel/hdg/rhdg/type/player: zone/unit>
function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type", "player"}
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type", "player", "twn", "loc"}
local outMsg = inMsg
local uHead = 0
for idx, aLocale in pairs(locales) do
@ -3053,6 +3034,23 @@ function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
elseif aLocale == "rhdg" and (responses) then
local offset = cfxZones.rspMapper360(uHead, #responses)
locString = dcsCommon.trim(responses[offset])
elseif aLocale == "twn" then
if twn and towns then locString = twn.closestTownTo(thePoint)
else locString = "!twn!" end
elseif aLocale == "loc" then
if twn and towns then
local name, data, dist = twn.closestTownTo(thePoint)
local units = "km"
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
if imperialUnits then
dist = mdist
units = "nm"
end
local bear = dcsCommon.compassPositionOfARelativeToB(thePoint, data.p)
locString = dist .. units .. " " .. bear .. " of " .. name
else locString = "!twn!" end
else
-- we have mgrs
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
@ -3345,6 +3343,24 @@ function dmlZone:getName() -- no cfxZones.bridge!
return self.name
end
function dmlZone:getCoalition()
-- automatically support masterOwner. Warning: cloners etc can reference itself!
if self.masterOwner then return self.masterOwner.owner end -- zone must exist
return self.owner
end
function cfxZones.getCoalition(theZone)
return theZone:getCoalition()
end
function dmlZone:getTypeName()
return "dmlZone"
end
function cfxZones.getTypeName(theZone)
return theZone:getTypeName()
end
function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really Z, don't get confused!!!!
theZone.linkedUnit = theUnit
if not dx then dx = 0 end
@ -3693,6 +3709,20 @@ function cfxZones.init()
-- much like verbose, all zones have owner
for n, aZone in pairs(cfxZones.zones) do
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)
if aZone:hasProperty("masterOwner") then
local mo = aZone:getStringFromZoneProperty("masterOwner", "forgotten master")
mo = dcsCommon.trim(mo)
if mo == "*" then mo = aZone.name end
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++fcxZones: WARNING: Master Owner <" .. mo .. "> for zone <" .. aZone.name .. "> does not exist!", 30)
else
aZone.masterOwner = mz
aZone.owner = mz.owner
end
end
end
-- enable all zone's verbose flags if present

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "2.3.0"
cloneZones.version = "2.4.0"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -56,6 +56,7 @@ cloneZones.respawnOnGroupID = true
(undocumented, just to provide lazy people with a migration
path) with wiper module
- using "wipe?" will now create a warning
2.4.0 - reworked masterOwner to fit with dmlZone
--]]--
--
@ -268,6 +269,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.cloneMethod = theZone:getStringFromZoneProperty("method", "inc") -- note string on number default
end
--[[--
if theZone:hasProperty("masterOwner") then
theZone.masterOwner = theZone:getStringFromZoneProperty( "masterOwner", "*")
theZone.masterOwner = dcsCommon.trim(theZone.masterOwner)
@ -285,7 +287,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
if not theMaster then
trigger.action.outText("clnZ: WARNING: cloner's <" .. theZone.name .. "> master owner named <" .. theZone.masterOwner .. "> does not exist!", 30)
end
theZone.masterOwner = theMaster
end
--]]--
theZone.turn = theZone:getNumberFromZoneProperty("turn", 0)
@ -692,7 +696,8 @@ function cloneZones.sameIDUnitData(theData)
end
function cloneZones.resolveOwnership(spawnZone, ctry)
if not spawnZone.masterOwner then return ctry end
if not spawnZone.masterOwner then return ctry end -- old code
--[[--
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
if not masterZone then
trigger.action.outText("+++clnZ: cloner " .. spawnZone.name .. " could not find master owner <" .. spawnZone.masterOwner .. ">", 30)
@ -702,8 +707,10 @@ function cloneZones.resolveOwnership(spawnZone, ctry)
if not masterZone.owner then
return ctry
end
--]]--
ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
-- ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
ctry = dcsCommon.getACountryForCoalition(spawnZone:getCoalition())
return ctry
end
@ -1694,6 +1701,8 @@ function cloneZones.hasLiveUnits(theZone)
end
function cloneZones.resolveOwningCoalition(theZone)
return theZone:getCoalition()
--[[--
if not theZone.masterOwner then return theZone.owner end
local masterZone = cfxZones.getZoneByName(theZone.masterOwner)
if not masterZone then
@ -1701,6 +1710,7 @@ function cloneZones.resolveOwningCoalition(theZone)
return theZone.owner
end
return masterZone.owner
--]]--
end
function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "4.0.0"
csarManager.version = "4.2.1"
csarManager.ups = 1
--[[-- VERSION HISTORY
@ -46,7 +46,11 @@ csarManager.ups = 1
3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data
4.0.0 - support for mainMenu
4.0.1 - increased verbosity
- fix for Jul-11 2024 DCS bugs
4.1.0 - support for DCS 2.9.6 dynamic spawns
4.2.0 - automatically support twn if present
4.2.1 - added Chinook to csar default set (via common)
INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES
@ -64,6 +68,8 @@ csarManager.requiredLibs = {
-- unitConfigs contain the config data for any helicopter
-- currently in the game. The Array is indexed by unit name
-- requires single-unit player groups
-- compatible with DCS 2.9.6 dcs dynamic spawns
csarManager.unitConfigs = {}
--
@ -181,7 +187,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end
if csarManager.useRanks then
-- local ranks = csarManager.ranks -- {"Lt", "Lt", "Lt", "Col", "Cpt", "WO", "WO"}
local myRank = dcsCommon.pickRandom(csarManager.ranks)
name = myRank .. " " .. name
end
@ -243,7 +248,6 @@ function csarManager.removeMission(theMission, pickup)
if aMission ~= theMission then
table.insert(newMissions, aMission)
else
-- csarManager.invokeRemovedMissionCallbacks(theMission)
if pickup then
csarManager.invokePickUpCallbacks(theMission)
end
@ -296,6 +300,7 @@ end
function csarManager.getUnitConfig(theUnit) -- will create new config if not existing
-- compatible with dynamic spawns for DCS 2.9.6
if not theUnit then
trigger.action.outText("+++csar: nil unit in get config!", 30)
return nil
@ -331,15 +336,22 @@ function csarManager:onEvent(event)
if not dcsCommon.isPlayerUnit(theUnit) then return end -- not a player unit
if csarManager.verbose then
trigger.action.outText("csarM: player event <" .. event.id .. "> -- (=<" .. dcsCommon.event2text(event.id) .. ">)", 30)
end
-- only proceed if troop carrier (no more helo checks, all troop carriers, so osprey and harrier can be used if so desired)
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
local ID = event.id
if ID == 4 then -- landed
if ID == 4 or ID == 55 then -- landed, runway touch
if csarManager.verbose then
trigger.action.outText("land event " .. ID .. "received.", 30)
end
csarManager.heloLanded(theUnit)
end
if ID == 3 or ID == 55 then -- take off, postponed take-off
if ID == 3 or ID == 54 then -- take off, runway take-off
csarManager.heloDeparted(theUnit)
end
@ -354,18 +366,15 @@ function csarManager:onEvent(event)
csarManager.setCommsMenu(theUnit)
-- we also need to make sure that there are no
-- more troopsOnBoard
local myName = theUnit:getName()
local conf = csarManager.getUnitConfig(theUnit)
conf.unit = theUnit
conf.troopsOnBoard = {}
local totalMass = cargoSuper.calculateTotalMassFor(myName)
-- now also set cargo weight for the unit
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
end
end
@ -698,6 +707,8 @@ end
function csarManager.setCommsMenu(theUnit)
-- add menu for this aircraft/group when it spawns
-- compatible with dynamic spawns for DCS 2.9.6
if not theUnit then return end
if not theUnit:isExist() then return end
@ -821,11 +832,19 @@ function csarManager.doListCSARRequests(args)
status = status .. " [" .. delta .. "]" -- remove me
end
end
local locinfo = ""
if twn and towns then
local village, data, dist = twn.closestTownTo(mission.zone.point)
dist = dist * 0.539957 -- nm conversion
dist = math.floor(dist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(mission.zone.point, data.p)
locinfo = ", " .. dist .. "nm " .. bear .. " of " .. village
end
if csarManager.vectoring then
report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..mission.dist .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
report = report .. "\n".. mission.name .. locinfo .. ", bearing " .. b .. ", " ..mission.dist .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
else
-- leave out vectoring
report = report .. "\n".. mission.name .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
report = report .. "\n".. mission.name .. locinfo .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
end
end
end

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.0.9"
dcsCommon.version = "3.1.2"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -26,7 +26,9 @@ dcsCommon.version = "3.0.9"
3.0.9 - new getOrigPositionByID()
- unitName2ID[] reverse lookup
- unitName2Heading
3.1.0 - updates to events, DCS update 7-11 2024 hardening
3.1.1 - added Chinook to troop carriers
3.1.2 - isTroopCarrier() hardening against DCS sillieness
--]]--
-- dcsCommon is a library of common lua functions
@ -39,7 +41,7 @@ dcsCommon.version = "3.0.9"
-- globals
dcsCommon.cbID = 0 -- callback id for simple callback scheduling
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D"} -- Ka-50, Apache and Gazelle can't carry troops
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D", "CH-47Fbl1"} -- Ka-50, Apache and Gazelle can't carry troops
dcsCommon.coalitionSides = {0, 1, 2}
dcsCommon.maxCountry = 86 -- number of countries defined in total
@ -707,7 +709,7 @@ dcsCommon.version = "3.0.9"
end
function dcsCommon.compassPositionOfARelativeToB(A, B)
-- warning: is REVERSE in order for bearing, returns a string like 'Sorth', 'Southwest'
-- warning: is REVERSE in order for bearing, returns a string like 'North', 'Southwest'
if not A then return "***error:A***" end
if not B then return "***error:B***" end
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360
@ -2566,9 +2568,10 @@ end
"Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
"unit task timeout", "unit task stage", -- 50
"subtask score", "extra score", "mission restart", "winner",
"postponed takeoff", "postponed land", -- 56
"max"}
"subtask score", "mission restart", "winner", -- 53
"runway takeoff", "runway touchdown", "LMS Restart", -- 56
"sim freeze", "sum unfreeze", "player start repair", "player end repair", --60
"max",} -- 61
if id > #events then return "Unknown (ID=" .. id .. ")" end
return events[id]
end
@ -2839,9 +2842,9 @@ end
function dcsCommon.isTroopCarrier(theUnit, carriers)
-- return true if conf can carry troups
if not theUnit then return false end
if not theUnit.getTypeName then return false end -- hardening against DCS sillieness
-- see if carriers contains "helo" and theUnit is a helo
if dcsCommon.arrayContainsString(carriers, "helo") or dcsCommon.arrayContainsString(carriers, "helos")then
if dcsCommon.arrayContainsString(carriers, "helo") or dcsCommon.arrayContainsString(carriers, "helos") then
local grp = theUnit:getGroup()
if grp:getCategory() == 1 then -- NOT category bug prone, is a group check
return true

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.0.4"
cfxHeloTroops.version = "3.1.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -8,31 +8,6 @@ cfxHeloTroops.requestRange = 500 -- meters
--
--[[--
VERSION HISTORY
1.1.3 - repaired forgetting 'wait-' when loading/disembarking
1.1.4 - corrected coalition bug in deployTroopsFromHelicopter
2.0.0 - added weight change when troops enter and leave the helicopter
- idividual troop capa max per helicopter
2.0.1 - lib loader verification
- uses dcsCommon.isTroopCarrier(theUnit)
2.0.2 - can now deploy from spawners with "requestable" attribute
2.1.0 - supports config zones
- check spawner legality by types
- updated types to include 2.7.6 additions to infantry
- updated types to include stinger/manpads
2.2.0 - minor maintenance (dcsCommon)
- (re?) connected readConfigZone (wtf?)
- persistence support
- made legalTroops entrirely optional and defer to dcsComon else
2.3.0 - interface with owned zones and playerScore when
- combat-dropping troops into non-owned owned zone.
- prevent auto-load from pre-empting loading csar troops
2.3.1 - added ability to self-define troopCarriers via config
2.4.0 - added missing support for attackZone orders (destination)
- eliminated cfxPlayer module import and all dependencies
- added support for groupTracker / limbo
- removed restriction to only apply to helicopters in anticipation of the C-130 Hercules appearing in the game
2.4.1 - new actionSound attribute, sound plays to group whenever
troops have boarded or disembarked
3.0.0 - added requestable cloner support
- harmonized spawning invocations across cloners and spawners
- dmlZones
@ -41,14 +16,10 @@ cfxHeloTroops.requestRange = 500 -- meters
3.0.2 - fixed a typo in in-air menu
3.0.3 - pointInZone check for insertion rather than radius
3.0.4 - also handles picking up troops with orders "captureandhold"
3.0.5 - worked around a new issues accessing a unit's name
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
--]]--
--
-- cfxHeloTroops -- a module to pick up and drop infantry.
-- Can be used with ANY aircraft, configured by default to be
-- restricted to troop-carrying helicopters.
-- might be configure to apply to any type you want using the
-- configuration zone.
cfxHeloTroops.requiredLibs = {
@ -284,9 +255,8 @@ function cfxHeloTroops.addConfigMenu(conf)
end
function cfxHeloTroops.setCommsMenu(theUnit)
-- depending on own load state, we set the command structure
-- it begins at 10-other, and has 'Assault Troops' as main menu with submenus
-- as required
-- compatible with DCS 2.9.6 dynamic spawns
-- set F10 Other.. menu for group
if not theUnit then return end
if not theUnit:isExist() then return end
@ -301,7 +271,7 @@ function cfxHeloTroops.setCommsMenu(theUnit)
local group = theUnit:getGroup()
local id = group:getID()
local conf = cfxHeloTroops.getUnitConfig(theUnit)
conf.id = id; -- we do this ALWAYS to it is current even after a crash
conf.id = id; -- we ALWAYS do this so it is current even after a crash
conf.unit = theUnit -- link back
-- ok, first, if we don't have an F-10 menu, create one
@ -819,10 +789,10 @@ function cfxHeloTroops:onEvent(theEvent)
local initiator = theEvent.initiator
if not initiator then return end -- not interested
local theUnit = initiator
local name = theUnit:getName()
-- see if this is a player aircraft
if not theUnit.getPlayerName then return end -- not a player
if not theUnit:getPlayerName() then return end -- not a player
local name = theUnit:getName() -- moved to a later
-- only for helicopters -- overridedden by troop carriers
-- we don't check for cat any more, so any airframe

View File

@ -1,5 +1,5 @@
jtacGrpUI = {}
jtacGrpUI.version = "3.0.0"
jtacGrpUI.version = "3.1.0"
jtacGrpUI.requiredLibs = {
"dcsCommon", -- always
"cfxZones",
@ -12,6 +12,7 @@ jtacGrpUI.requiredLibs = {
- clean-up
- jtacSound
3.0.0 - support for attachTo:
3.1.0 - support for DCS 2.0.6 dynamic player spwans
--]]--
-- find & command cfxGroundTroops-based jtacs
@ -151,7 +152,7 @@ function jtacGrpUI.setCommsMenu(theGroup)
end
local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we always do this ALWAYS
conf.id = theGroup:getID(); -- we always do this
if jtacGrpUI.simpleCommands then
-- we install directly in F-10 other
@ -294,25 +295,29 @@ function jtacGrpUI:onEvent(theEvent)
if not theEvent then return end
local theUnit = theEvent.initiator
if not theUnit then return end
if not theUnit.getName then return end -- dcs 2.9.6 jul-11 fix
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)
local id = theEvent.id
if id == 15 then
-- 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
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

View File

@ -1,5 +1,5 @@
messenger = {}
messenger.version = "3.1.0"
messenger.version = "3.2.0"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -13,6 +13,8 @@ messenger.messengers = {}
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
3.1.0 - msgGroup supports multiple groups, separated by comma
- msgUnit supports multiple units, separated by comma
3.2.0 - loc and twn wildcard support (inherited from zones)
--]]--
function messenger.addMessenger(theZone)
@ -165,7 +167,7 @@ end
--
-- reat attributes
-- read attributes
--
function messenger.createMessengerWithZone(theZone)
local aMessage = theZone:getStringFromZoneProperty("message", "")

View File

@ -1,5 +1,5 @@
ownAll = {}
ownAll.version = "1.0.0"
ownAll.version = "1.1.0"
ownAll.verbose = false
ownAll.requiredLibs = {
"dcsCommon", -- always
@ -9,7 +9,7 @@ ownAll.requiredLibs = {
--[[--
VERSION HISTORY
- 1.0.0 - Initial version
- 1.1.0 - dml:masterOwner / getCoalition() updates
--]]--
ownAll.zones = {}
@ -69,13 +69,13 @@ function ownAll.calcState(theZone)
local blueNum = 0
local allSame = true
if #theZone.zones < 1 then return -1, 0, 0 end
local s = theZone.zones[1].owner
local s = theZone.zones[1]:getCoalition() -- owner
if not s then
trigger.action.outText("+++oAll: zone <" .. theZone.zones[1].name .."> has no owner (?)", 30)
s = -1
end
for idx, aZone in pairs (theZone.zones) do
local s2 = aZone.owner
local s2 = aZone:getCoalition() -- .owner
if not s2 then
trigger.action.outText("+++oAll: zone <" .. aZone.name .."> has no owner (?)", 30)
s2 = -1

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {}
cfxOwnedZones.version = "2.3.1"
cfxOwnedZones.version = "2.4.0"
cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
@ -43,6 +43,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
- title attribute
- code clean-up
2.3.1 - restored getNearestOwnedZoneToPoint
2.4.0 - dmlZones masterOwner update
--]]--
cfxOwnedZones.requiredLibs = {
"dcsCommon",
@ -180,6 +182,7 @@ function cfxOwnedZones.addOwnedZone(aZone)
aZone.neutralFill = aZone:getRGBAVectorFromZoneProperty("neutralFill", cfxOwnedZones.neutralFill)
-- masterOwner
--[[--
if aZone:hasProperty("masterOwner") then
local masterZone = aZone:getStringFromZoneProperty("masterOwner", "cfxNoneErr")
local theMaster = cfxZones.getZoneByName(masterZone)
@ -193,6 +196,7 @@ function cfxOwnedZones.addOwnedZone(aZone)
end
end
end
--]]--
aZone.announcer = aZone:getBoolFromZoneProperty("announcer", cfxZones.announcer)
if aZone:hasProperty("announce") then
@ -348,7 +352,7 @@ function cfxOwnedZones.update()
for idz, theZone in pairs(cfxOwnedZones.zones) do
theZone.numRed = 0
theZone.numBlue = 0
local lastOwner = theZone.owner
local lastOwner = theZone.owner -- do NOT use dml:getCoalition()!
if not lastOwner then
trigger.action.outText("+++owdZ: WARNING - zone <" .. theZone.name .. "> has NIL owner", 30)
return
@ -472,7 +476,7 @@ function cfxOwnedZones.update()
-- we do nothing
elseif theZone.masterOwner then
-- inherit from my master
newOwner = theZone.masterOwner.owner
newOwner = theZone:getCoalition() -- theZone.masterOwner.owner
elseif theZone.numRed < 1 and theZone.numBlue < 1 then
-- no troops here. Become neutral?
if theZone.numKeep < 1 then

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "3.3.0"
cfxPlayerScore.version = "3.3.1"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
@ -16,6 +16,8 @@ cfxPlayerScore.firstSave = true -- to force overwrite
3.1.0 - shared data for persistence
3.2.0 - integration with bank
3.3.0 - case INsensitivity for all typeScore objects
3.3.1 - fixes for DCS oddity in events after update
- cleanup
--]]--
cfxPlayerScore.requiredLibs = {
@ -104,15 +106,12 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi
if theZone.featNum == 0 then
canAward = false
end
if theZone.featType ~= featType then
canAward = false
end
if not (theZone.coalition == 0 or theZone.coalition == coa) then
canAward = false
end
if featType == "PVP" then
-- make sure kill is pvp kill
if not victim then canAward = false
@ -122,22 +121,17 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi
canAward = false
end
end
if not cfxZones.pointInZone(loc, theZone) then
canAward = false
end
if theZone.ppOnce then
if theZone.awardedTo[name] then
canAward = false
end
end
if canAward then
table.insert(theFeats, theZone) -- jupp, add it
else
table.insert(theFeats, theZone) -- jupp, add it
end
end
return theFeats
end
@ -166,7 +160,11 @@ function cfxPlayerScore.preprocessWildcards(inMsg, aUnit, aVictim)
theMsg = theMsg:gsub("<kplayer>", "unknown AI")
end
end
theMsg = theMsg:gsub("<unit>", aVictim:getName())
if aVictim.getName then
theMsg = theMsg:gsub("<unit>", aVictim:getName())
else
theMsg = theMsg:gsub("<unit>", "*?*") -- dcs oddity
end
theMsg = theMsg:gsub("<type>", aVictim:getTypeName())
-- victim may not have group. guard against that
-- happens if unit 'cooks off'
@ -205,16 +203,15 @@ function cfxPlayerScore.cat2BaseScore(inCat)
if inCat == 2 then return cfxPlayerScore.ground end -- ground
if inCat == 3 then return cfxPlayerScore.ship end -- ship
if inCat == 4 then return cfxPlayerScore.train end -- train
trigger.action.outText("+++scr c2bs: unknown category for lookup: <" .. inCat .. ">, returning 1", 30)
return 1
end
function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if not inVictim then return 0 end
if not killSide then killSide = -1 end
local inName = inVictim:getName()
local inName
if inVictim.getName then inName = inVictim:getName() else inName = "*?*" end -- dcs oddity
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: ob2sc entry to resolve name <" .. inName .. ">", 30)
end
@ -251,13 +248,10 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
objectScore = cfxPlayerScore.typeScore[theType:upper()]
end
end
if type(objectScore) == "string" then
objectScore = tonumber(objectScore)
end
if objectScore then return objectScore end
-- we now try and get the general type of the killed object
local desc = inVictim:getDesc() -- Object.getDesc(inVictim)
local attributes = desc.attributes
@ -268,7 +262,6 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if attributes["Ships"] then return cfxPlayerScore.ship end
-- trains can't be detected
end
if not objectScore then return 0 end
return objectScore
end
@ -277,34 +270,29 @@ function cfxPlayerScore.unit2score(inUnit)
local vicGroup = inUnit:getGroup()
local vicCat = vicGroup:getCategory()-- group cat, not 2.9 affected
local vicType = inUnit:getTypeName()
local vicName = inUnit:getName()
local vicName
if inUnit.getName then vicName = inUnit:getName() else vicName = "*?*" end
if type(vicName) == "number" then vicName = tostring(vicName) end
-- simply extend by adding items to the typescore table.concat
-- we first try by unit name. This allows individual
-- named hi-value targets to have individual scores
local uScore = cfxPlayerScore.typeScore[vicName:upper()]
-- see if all members of group score
if (not uScore) and vicGroup then
local grpName = vicGroup:getName()
uScore = cfxPlayerScore.typeScore[grpName:upper()]
end
if not uScore then
-- WE NOW TRY TO ACCESS BY VICTIM'S TYPE STRING
uScore = cfxPlayerScore.typeScore[vicType:upper()]
else
end
if type(uScore) == "string" then
-- convert string to number
uScore = tonumber(uScore)
end
if not uScore then uScore = 0 end
if uScore > 0 then return uScore end
-- only apply base scores when the lookup did not give a result
uScore = cfxPlayerScore.cat2BaseScore(vicCat)
return uScore
@ -312,7 +300,7 @@ end
function cfxPlayerScore.getPlayerScore(playerName)
local thePlayerScore = cfxPlayerScore.playerScore[playerName]
if thePlayerScore == nil then
if not thePlayerScore then
thePlayerScore = {}
thePlayerScore.name = playerName
thePlayerScore.score = 0 -- score
@ -379,7 +367,6 @@ function cfxPlayerScore.doLogTypeKill(playerName, thePlayerScore, theType)
killCount = killCount + 1
thePlayerScore.totalKills = thePlayerScore.totalKills + 1
thePlayerScore.killTypes[theType] = killCount
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
end
@ -390,14 +377,12 @@ function cfxPlayerScore.logKillForPlayer(playerName, theUnit)
if not playerName then return end
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
local theType = theUnit:getTypeName()
if cfxPlayerScore.deferred then
-- just queue it
table.insert(thePlayerScore.killQueue, theType)
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write-through. why? because it may be a new entry.
return
end
cfxPlayerScore.doLogTypeKill(playerName, thePlayerScore, theType)
end
@ -410,9 +395,7 @@ function cfxPlayerScore.doLogFeat(playerName, thePlayerScore, theFeat)
featCount = featCount + 1
thePlayerScore.totalFeats = thePlayerScore.totalFeats + 1
thePlayerScore.featTypes[theFeat] = featCount
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
end
function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
@ -421,34 +404,29 @@ function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
if not theFeat then return end
if not playerName then return end
-- access player's record. will alloc if new by itself
if coa then
local disclaim = ""
if cfxPlayerScore.deferred then disclaim = " (award pending)" end
trigger.action.outTextForCoalition(coa, playerName .. " achieved " .. theFeat .. disclaim, 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
if cfxPlayerScore.deferred then
table.insert(thePlayerScore.featQueue, theFeat)
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
return
end
cfxPlayerScore.doLogFeat(playerName, thePlayerScore, theFeat)
end
function cfxPlayerScore.playerScore2text(thePlayerScore, scoreOnly)
if not scoreOnly then scoreOnly = false end
local desc = thePlayerScore.name .. " statistics:\n"
if cfxPlayerScore.reportScore then
desc = desc .. " - score: ".. thePlayerScore.score .. " - total kills: " .. thePlayerScore.totalKills .. "\n"
if scoreOnly then
return desc
end
-- now go through all kills
desc = desc .. "\nKills by type:\n"
if dcsCommon.getSizeOfTable(thePlayerScore.killTypes) < 1 then
@ -474,16 +452,13 @@ function cfxPlayerScore.playerScore2text(thePlayerScore, scoreOnly)
desc = desc .. "\n"
end
end
if cfxPlayerScore.reportScore and thePlayerScore.scoreaccu > 0 then
desc = desc .. "\n - unclaimed score: " .. thePlayerScore.scoreaccu .."\n"
end
local featCount = dcsCommon.getSizeOfTable(thePlayerScore.featQueue)
if cfxPlayerScore.reportFeats and featCount > 0 then
desc = desc .. " - unclaimed feats: " .. featCount .."\n"
end
return desc
end
@ -508,7 +483,6 @@ function cfxPlayerScore.scoreSummaryForPlayersOfCoalition(side)
if count < 1 then
desc = desc .. " (No score yet)"
end
desc = desc .. "\n"
return desc
end
@ -537,17 +511,14 @@ function cfxPlayerScore.scoreTextForAllPlayers(ranked)
isFirst = false
rank = rank + 1
end
if dcsCommon.getSizeOfTable(theScores) < 1 then
theText = theText .. " (No score yet)\n"
end
if cfxPlayerScore.reportCoalition then
--theText = theText .. "\n"
theText = theText .. "\nRED total: " .. cfxPlayerScore.coalitionScore[1]
theText = theText .. "\nBLUE total: " .. cfxPlayerScore.coalitionScore[2]
end
return theText
end
@ -560,7 +531,11 @@ function cfxPlayerScore.isNamedUnit(theUnit)
else
-- WARNING: NO EXIST CHECK DONE!
-- after kill, unit is dead, so will no longer exist!
theName = theUnit:getName()
if theUnit.getName then
theName = theUnit:getName()
else
theName = "*?*"
end
if not theName then return false end
end
if cfxPlayerScore.typeScore[theName:upper()] then
@ -576,9 +551,7 @@ function cfxPlayerScore.awardScoreTo(killSide, theScore, killerName)
else
playerScore = cfxPlayerScore.updateScoreForPlayer(killerName, theScore)
end
if not cfxPlayerScore.reportScore then return end
if cfxPlayerScore.announcer then
if (theScore > 0) and cfxPlayerScore.deferred then
thePlayerRecord = cfxPlayerScore.getPlayerScore(killerName) -- re-read after write
@ -614,9 +587,9 @@ function cfxPlayerScore.preProcessor(theEvent)
if theEvent.initiator == nil then
return false
end
-- check if this was FORMERLY a player plane
local theUnit = theEvent.initiator
if not theUnit.getName then return end -- fix for DCS update bug
local uName = theUnit:getName()
if cfxPlayerScore.unit2player[uName] then
-- this requires special IMMEDIATE handling when event is
@ -627,19 +600,16 @@ function cfxPlayerScore.preProcessor(theEvent)
theEvent.id == 30 or -- unit loss
theEvent.id == 6 then -- eject
-- these can lead to a pilot demerit
--trigger.action.outText("PREPROC plane player extra event - possible death", 30)
-- event does NOT have a player
cfxPlayerScore.handlePlayerDeath(theEvent)
return false
end
end
-- initiator must be player
if not theUnit.getPlayerName or
not theUnit:getPlayerName() then
return false
end
if theEvent.id == 28 then
-- we only are interested in kill events where
-- there is a target
@ -650,20 +620,17 @@ function cfxPlayerScore.preProcessor(theEvent)
end
return false
end
-- if there are kill zones, we filter all kills that happen outside of kill zones
if #cfxPlayerScore.killZones > 0 then
local pLoc = theUnit:getPoint()
local tLoc = theEvent.target:getPoint()
local isIn, percent, dist, theZone = cfxZones.pointInOneOfZones(tLoc, cfxPlayerScore.killZones)
if not isIn then
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: kill detected, but target <" .. theEvent.target:getName() .. "> was outside of any kill zones", 30)
end
return false
end
if theZone.duet and not cfxZones.pointInZone(pLoc, theZone) then
-- player must be in same zone but was not
if cfxPlayerScore.verbose then
@ -686,7 +653,7 @@ function cfxPlayerScore.preProcessor(theEvent)
-- take off. overwrites timestamp for last landing
-- so a blipping t/o does nor count. Pre-proc only
if theEvent.id == 3 then
if theEvent.id == 3 or theEvent.id == 54 then
local now = timer.getTime()
local playerName = theUnit:getPlayerName()
cfxPlayerScore.lastPlayerLanding[playerName] = now -- overwrite
@ -696,7 +663,7 @@ function cfxPlayerScore.preProcessor(theEvent)
-- landing can score. but only the first landing in x seconds
-- landing in safe zone promotes any queued scores to
-- permanent if enabled, then nils queue
if theEvent.id == 4 then
if theEvent.id == 4 or theEvent.id == 55 then
-- player landed. filter multiple landed events
local now = timer.getTime()
local playerName = theUnit:getPlayerName()
@ -704,7 +671,7 @@ function cfxPlayerScore.preProcessor(theEvent)
cfxPlayerScore.lastPlayerLanding[playerName] = now -- overwrite
if lastLanding and lastLanding + cfxPlayerScore.delayBetweenLandings > now then
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: Player <" .. playerName .. "> touch-down ignored: too soon.", 30)
trigger.action.outText("+++pScr: Player <" .. playerName .. "> touch-down ignored: too soon after last.", 30)
trigger.action.outText("now is <" .. now .. ">, between is <" .. cfxPlayerScore.delayBetweenLandings .. ">, last + between is <" .. lastLanding + cfxPlayerScore.delayBetweenLandings .. ">", 30)
end
-- filter this event
@ -731,23 +698,18 @@ function cfxPlayerScore.checkKillFeat(name, killer, victim, fratricide)
if not fratricide then fratricide = false end
local theLoc = victim:getPoint() -- vic's loc is relevant for zone check
local coa = killer:getCoalition()
local killFeats = cfxPlayerScore.featsForLocation(name, theLoc, coa,"KILL", killer, victim)
if (not fratricide) and #killFeats > 0 then
-- use the feat description
-- we may want to use closest, currently simply the first
theFeatZone = killFeats[1]
local desc = cfxPlayerScore.evalFeatDescription(name, theFeatZone, killer, victim) -- updates awardedTo
cfxPlayerScore.logFeatForPlayer(name, desc, playerSide)
theScore = cfxPlayerScore.getPlayerScore(name) -- re-read after write
if cfxPlayerScore.verbose then
trigger.action.outText("Kill feat awarded/queued for <" .. name .. ">", 30)
end
end
end
function cfxPlayerScore.killDetected(theEvent)
@ -767,7 +729,6 @@ function cfxPlayerScore.killDetected(theEvent)
-- was it a player kill?
local pk = dcsCommon.isPlayerUnit(victim)
-- was it a scenery object?
local wasBuilding = dcsCommon.isSceneryObject(victim)
if wasBuilding then
@ -799,7 +760,9 @@ function cfxPlayerScore.killDetected(theEvent)
--if not victim.getGroup then
if isStO then
-- static objects have no group
local staticName = victim:getName() -- on statics, this returns
local staticName
if victim.getName then staticName = victim:getName() -- on statics, this returns
else staticName = "*?*" end
-- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim, killSide)
@ -817,7 +780,6 @@ function cfxPlayerScore.killDetected(theEvent)
else
-- no score, no mentions
end
if not fraternicide then
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
end
@ -834,12 +796,7 @@ function cfxPlayerScore.killDetected(theEvent)
trigger.action.outText("+++scr: strange stuff:cat, outta here", 30)
return
end
local unitScore = cfxPlayerScore.unit2score(victim)
-- see which weapon was used. gun kills score 2x
-- local killMeth = "" -- meth is currently not defined
-- local killWeap = theEvent.weapon -- not supported either
local unitScore = cfxPlayerScore.unit2score(victim)
if pk then -- player kill - add player's name
vicDesc = victim:getPlayerName() .. " in " .. vicDesc
scoreMod = scoreMod * cfxPlayerScore.pkMod
@ -869,17 +826,14 @@ function cfxPlayerScore.killDetected(theEvent)
trigger.action.outTextForCoalition(killSide, killerName .. " reports killing strategic unit '" .. victim:getName() .. "'", 30)
end
end
local totalScore = unitScore * scoreMod
-- if the score is negative, awardScoreTo will automatically
-- make it immediate, else depending on deferred
cfxPlayerScore.awardScoreTo(killSide, totalScore, killerName)
if not fraternicide then
-- only award kill feats for kills of the enemy
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
end
end
function cfxPlayerScore.handlePlayerLanding(theEvent)
@ -890,13 +844,11 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: Player <" .. playerName .. "> landed", 30)
end
local theScore = cfxPlayerScore.getPlayerScore(playerName)
-- see if a feat is available for this landing
local landingFeats = cfxPlayerScore.featsForLocation(playerName, theLoc, playerSide,"LANDING")
-- first, scheck if landing is awardable, and if so,
-- first, check if landing is awardable, and if so,
-- award the landing
if cfxPlayerScore.landing > 0 or #landingFeats > 0 then
-- yes, landings are awarded a score. do before
@ -914,7 +866,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
desc = desc .. " aircraft"
end
end
cfxPlayerScore.updateScoreForPlayer(playerName, cfxPlayerScore.landing)
cfxPlayerScore.logFeatForPlayer(playerName, desc, playerSide)
theScore = cfxPlayerScore.getPlayerScore(playerName) -- re-read after write
@ -922,7 +873,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
trigger.action.outText("Landing feat awarded/queued for <" .. playerName .. ">", 30)
end
end
-- see if we are using deferred scoring, else can end right now
if not cfxPlayerScore.deferred then
return
@ -930,7 +880,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
-- only continue if there is anything to award
local killSize = dcsCommon.getSizeOfTable(theScore.killQueue)
local featSize = dcsCommon.getSizeOfTable(theScore.featQueue)
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: prepping deferred score for <" .. playerName ..">", 30)
end
@ -948,15 +897,17 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
if (theZone.owner == coa) or (theZone.owner == 0) or (theZone.owner == nil) then
if cfxZones.pointInZone(loc, theZone) then
isSafe = true
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, my coa=<" .. coa .. ">, LANDED SAFELY", 30)
end
end
else
if cfxPlayerScore.verbose then
trigger.action.outText("+++pSc: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, my coa=<" .. coa .. ">, no owner match")
if cfxPlayerScore.verbose and cfxZones.pointInZone(loc, theZone) then
trigger.action.outText("+++pScr: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, player unit <" .. theUnit:getName() .. ">, my coa=<" .. coa .. ">, no owner match", 30)
end
end
end
end
if not isSafe then
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: deferred, but not inside score safe zone.", 30)
@ -975,7 +926,6 @@ function cfxPlayerScore.scheduledAward(args)
-- called with player name and unit name in args
local playerName = args[1]
local unitName = args[2]
local theUnit = Unit.getByName(unitName)
if not theUnit or
not Unit.isExist(theUnit)
@ -984,18 +934,15 @@ function cfxPlayerScore.scheduledAward(args)
trigger.action.outText("Player <" .. playerName .. "> lost score.", 30)
return
end
local uid = theUnit:getID()
if theUnit:inAir() then
trigger.action.outTextForUnit(uid, "Can't award score to <" .. playerName .. ">: unit not on the ground.", 30)
return
end
if theUnit:getLife() < 1 then
trigger.action.outTextForUnit(uid, "Can't award score to <" .. playerName .. ">: unit did not survive landing.", 30)
return -- needs to reslot, don't have to nil player score
end
-- see if player is *still* within a scoreSafe zone
local loc = theUnit:getPoint()
local coa = theUnit:getCoalition()
@ -1008,12 +955,10 @@ function cfxPlayerScore.scheduledAward(args)
end
end
end
if not isSafe then
trigger.action.outTextForUnit(uid, "Can't award score for <" .. playerName .. ">, not in safe zone.", 30)
return
end
local theScore = cfxPlayerScore.getPlayerScore(playerName)
local playerSide = dcsCommon.playerName2Coalition(playerName)
if playerSide < 1 then
@ -1023,14 +968,12 @@ function cfxPlayerScore.scheduledAward(args)
if dcsCommon.getSizeOfTable(theScore.killQueue) < 1 and
dcsCommon.getSizeOfTable(theScore.featQueue) < 1 and
theScore.scoreaccu < 1 then
-- player changed planes or
-- there was nothing to award
-- player changed planes or there was nothing to award
trigger.action.outTextForUnit(uid, "Thank you, " .. playerName .. ", no scores or feats pending.", 30)
return
end
local hasAward = false
-- when we get here we award all scores, kills, and feats
local desc = "\nPlayer " .. playerName .. " is awarded:\n"
-- score and total score
@ -1044,8 +987,7 @@ function cfxPlayerScore.scheduledAward(args)
end
theScore.scoreaccu = 0
hasAward = true
end
end
if cfxPlayerScore.verbose then
trigger.action.outText("Iterating kill q <" .. dcsCommon.getSizeOfTable(theScore.killQueue) .. "> and feat q <" .. dcsCommon.getSizeOfTable(theScore.featQueue) .. ">", 30)
end
@ -1059,7 +1001,6 @@ function cfxPlayerScore.scheduledAward(args)
hasAward = true
end
theScore.killQueue = {}
-- iterate feats
if dcsCommon.getSizeOfTable(theScore.featQueue) > 0 then
desc = desc .. " confirmed feats:\n"
@ -1070,11 +1011,9 @@ function cfxPlayerScore.scheduledAward(args)
hasAward = true
end
theScore.featQueue = {}
if cfxPlayerScore.reportCoalition then
desc = desc .. "\nCoalition Total: " .. cfxPlayerScore.coalitionScore[playerSide]
end
-- output score
desc = desc .. "\n"
if hasAward then
@ -1089,11 +1028,9 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
-- only counts once
local theUnit = theEvent.initiator
local uName = theUnit:getName()
if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: LOA/player death handler entry for <" .. uName .. ">", 30)
end
local pName = cfxPlayerScore.unit2player[uName]
if pName then
-- this was a player name with link still live.
@ -1113,14 +1050,12 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
trigger.action.outText("+++pScr - no action for LOA", 30)
end
end
end
function cfxPlayerScore.handlePlayerEvent(theEvent)
if theEvent.id == 28 then
-- kill from player detected.
cfxPlayerScore.killDetected(theEvent)
elseif theEvent.id == 15 then -- birth
-- access player score for player. this will
-- allocate if doesn't exist. Any player ever
@ -1130,7 +1065,6 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
local playerName = thePlayerUnit:getPlayerName()
local theScore = cfxPlayerScore.getPlayerScore(playerName)
-- now re-init feat and score queues
if theScore.scoreaccu and theScore.scoreaccu > 0 then
trigger.action.outTextForCoalition(playerSide, "Player " .. playerName .. ", score of <" .. theScore.scoreaccu .. "> points discarded.", 30)
end
@ -1146,9 +1080,10 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
-- write back
cfxPlayerScore.setPlayerScore(playerName, theScore)
elseif theEvent.id == 4 then -- land
elseif theEvent.id == 4 or theEvent.id == 55 then -- land
-- see if plane is still connected to player
local theUnit = theEvent.initiator
if not theUnit.getName then return end -- dcs oddity precaution
local uName = theUnit:getName()
if cfxPlayerScore.unit2player[uName] then
-- is filtered if too soon after last take-off/landing
@ -1158,7 +1093,6 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
trigger.action.outText("+++pScr: filtered landing for <" .. uName .. ">: player no longer linked to unit", 30)
end
end
end
end
@ -1167,69 +1101,51 @@ function cfxPlayerScore.readConfigZone(theZone)
-- default scores
cfxPlayerScore.aircraft = theZone:getNumberFromZoneProperty("aircraft", 50)
cfxPlayerScore.helo = theZone:getNumberFromZoneProperty("helo", 40)
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
cfxPlayerScore.ship = theZone:getNumberFromZoneProperty("ship", 80)
cfxPlayerScore.train = theZone:getNumberFromZoneProperty( "train", 5)
cfxPlayerScore.landing = theZone:getNumberFromZoneProperty("landing", 0) -- if > 0 then feat
cfxPlayerScore.pkMod = theZone:getNumberFromZoneProperty( "pkMod", 1) -- factor for killing a player
cfxPlayerScore.ffMod = theZone:getNumberFromZoneProperty( "ffMod", -2) -- factor for friendly fire
cfxPlayerScore.planeLoss = theZone:getNumberFromZoneProperty("planeLoss", -10) -- points added when player's plane crashes
cfxPlayerScore.announcer = theZone:getBoolFromZoneProperty("announcer", true)
if theZone:hasProperty("badSound") then
cfxPlayerScore.badSound = theZone:getStringFromZoneProperty("badSound", "<nosound>")
end
if theZone:hasProperty("scoreSound") then
cfxPlayerScore.scoreSound = theZone:getStringFromZoneProperty("scoreSound", "<nosound>")
end
-- triggering saving scores
if theZone:hasProperty("saveScore?") then
cfxPlayerScore.saveScore = theZone:getStringFromZoneProperty("saveScore?", "none")
cfxPlayerScore.lastSaveScore = trigger.misc.getUserFlag(cfxPlayerScore.saveScore)
cfxPlayerScore.incremental = theZone:getBoolFromZoneProperty("incremental", false) -- incremental saves
end
-- triggering show all scores
if theZone:hasProperty("showScore?") then
cfxPlayerScore.showScore = theZone:getStringFromZoneProperty("showScore?", "none")
cfxPlayerScore.lastShowScore = trigger.misc.getUserFlag(cfxPlayerScore.showScore)
end
cfxPlayerScore.rankPlayers = theZone:getBoolFromZoneProperty("rankPlayers", false)
cfxPlayerScore.scoreOnly = theZone:getBoolFromZoneProperty("scoreOnly", true)
cfxPlayerScore.deferred = theZone:getBoolFromZoneProperty("deferred", false)
cfxPlayerScore.delayAfterLanding = theZone:getNumberFromZoneProperty("delayAfterLanding", 10)
cfxPlayerScore.scoreFileName = theZone:getStringFromZoneProperty("scoreFileName", "Player Scores")
cfxPlayerScore.reportScore = theZone:getBoolFromZoneProperty("reportScore", true)
cfxPlayerScore.reportFeats = theZone:getBoolFromZoneProperty("reportFeats", true)
cfxPlayerScore.reportCoalition = theZone:getBoolFromZoneProperty("reportCoalition", false) -- also show coalition score
cfxPlayerScore.noGrief = theZone:getBoolFromZoneProperty( "noGrief", true) -- noGrief = only add positive score
if theZone:hasProperty("redScore#") then
cfxPlayerScore.redScoreOut = theZone:getStringFromZoneProperty("redScore#")
theZone:setFlagValue(cfxPlayerScore.redScoreOut, cfxPlayerScore.coalitionScore[1])
end
if theZone:hasProperty("blueScore#") then
cfxPlayerScore.blueScoreOut = theZone:getStringFromZoneProperty("blueScore#")
theZone:setFlagValue(cfxPlayerScore.blueScoreOut, cfxPlayerScore.coalitionScore[2])
end
if theZone:hasProperty("sharedData") then
cfxPlayerScore.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
cfxPlayerScore.score2finance = theZone:getNumberFromZoneProperty("score2finance", 1) -- factor to convert points to bank finance
end
@ -1287,11 +1203,9 @@ function cfxPlayerScore.loadData()
end
end
end
--
-- save scores (text file)
--
function cfxPlayerScore.saveScores(theText, name)
if not _G["persistence"] then
trigger.action.outText("+++pScr: persistence module required to save scores. Here are the scores that I would have saved to <" .. name .. ">:\n", 30)
@ -1331,7 +1245,6 @@ function cfxPlayerScore.saveScoreToFile()
-- local built score table
local ranked = cfxPlayerScore.rankPlayers
local theText = cfxPlayerScore.scoreTextForAllPlayers(ranked)
-- save to disk
cfxPlayerScore.saveScores(theText, cfxPlayerScore.scoreFileName)
end
@ -1341,14 +1254,12 @@ function cfxPlayerScore.showScoreToAll()
local theText = cfxPlayerScore.scoreTextForAllPlayers(ranked)
trigger.action.outText(theText, 30)
end
--
-- Update
--
function cfxPlayerScore.update()
-- re-invoke in 1 second
timer.scheduleFunction(cfxPlayerScore.update, {}, timer.getTime() + 1)
-- see if someone banged on saveScore
if cfxPlayerScore.saveScore then
if cfxZones.testZoneFlag(cfxPlayerScore, cfxPlayerScore.saveScore, "change", "lastSaveScore") then
@ -1358,7 +1269,6 @@ function cfxPlayerScore.update()
cfxPlayerScore.saveScoreToFile()
end
end
-- showScore perhaps?
if cfxPlayerScore.showScore then
if cfxZones.testZoneFlag(cfxPlayerScore, cfxPlayerScore.showScore, "change", "lastShowScore") then
@ -1368,7 +1278,6 @@ function cfxPlayerScore.update()
cfxPlayerScore.showScoreToAll()
end
end
-- check score flags
if cfxPlayerScore.blueTriggerFlags then
local coa = 2
@ -1377,13 +1286,11 @@ function cfxPlayerScore.update()
if tVal ~= newVal then
-- score!
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName]
cfxPlayerScore.blueTriggerFlags[tName] = newVal
cfxPlayerScore.blueTriggerFlags[tName] = newVal
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
-- bank it if exists
local amount
if bank and bank.addFunds then
@ -1402,17 +1309,12 @@ function cfxPlayerScore.update()
local newVal = trigger.misc.getUserFlag(tName)
if tVal ~= newVal then
-- score!
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName]
cfxPlayerScore.redTriggerFlags[tName] = newVal
--if bank and bank.addFunds then
-- bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
--end
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
-- bank it if exists
local amount
if bank and bank.addFunds then
@ -1429,7 +1331,6 @@ function cfxPlayerScore.update()
if cfxPlayerScore.redScoreOut then
cfxZones.setFlagValue(cfxPlayerScore.redScoreOut, cfxPlayerScore.coalitionScore[1], cfxPlayerScore)
end
if cfxPlayerScore.blueScoreOut then
cfxZones.setFlagValue(cfxPlayerScore.blueScoreOut, cfxPlayerScore.coalitionScore[2], cfxPlayerScore)
end
@ -1448,19 +1349,8 @@ function cfxPlayerScore.start()
-- identify and process a score table zones
local theZone = cfxZones.getZoneByName("playerScoreTable")
if theZone then
-- trigger.action.outText("Reading custom player score table", 30)
-- read all into my types registry, replacing whatever is there
cfxPlayerScore.typeScore = theZone:getAllZoneProperties(true) -- true = get all properties in UPPER case
-- local n = dcsCommon.getSizeOfTable(cfxPlayerScore.typeScore)
-- trigger.action.outText("Table has <" .. n .. "> entries:", 30)
if true then
--trigger.action.outText("Custom PlayerScore Type Score Table:", 30)
for name, val in pairs (cfxPlayerScore.typeScore) do
-- trigger.action.outText("ps[" .. name .. "]=<" .. val .. ">", 30)
end
end
else
--trigger.action.outText("No custom score defined", 30)
end
-- read score tiggers and values
@ -1493,43 +1383,37 @@ function cfxPlayerScore.start()
end
cfxPlayerScore.blueTriggerFlags[tName] = trigger.misc.getUserFlag(tName)
end
end
end
-- now read my config zone
local theZone = cfxZones.getZoneByName("playerScoreConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("playerScoreConfig")
end
cfxPlayerScore.readConfigZone(theZone)
-- read all scoreSafe zones
local safeZones = cfxZones.zonesWithProperty("scoreSafe")
for k, aZone in pairs(safeZones) do
cfxPlayerScore.addSafeZone(aZone)
end
-- read all feat zones
local featZones = cfxZones.zonesWithProperty("feat")
for k, aZone in pairs(featZones) do
cfxPlayerScore.addFeatZone(aZone)
end
end
-- read all kill zones
local killZones = cfxZones.zonesWithProperty("killZone")
for k, aZone in pairs(killZones) do
cfxPlayerScore.addKillZone(aZone)
end
-- check that deferred has scoreSafe zones
if cfxPlayerScore.deferred and dcsCommon.getSizeOfTable(cfxPlayerScore.safeZones) < 1 then
trigger.action.outText("+++pScr: WARNING - deferred scoring active but no 'scoreSafe' zones set!", 30)
end
-- subscribe to events and use dcsCommon's handler structure
dcsCommon.addEventHandler(cfxPlayerScore.handlePlayerEvent,
cfxPlayerScore.preProcessor,
cfxPlayerScore.postProcessor)
cfxPlayerScore.postProcessor)
-- now load all save data and populate map with troops that
-- we deployed last save.
if persistence then
@ -1543,7 +1427,6 @@ function cfxPlayerScore.start()
-- start update
cfxPlayerScore.update()
trigger.action.outText("cfxPlayerScore v" .. cfxPlayerScore.version .. " started", 30)
return true
end

View File

@ -1,5 +1,5 @@
cfxPlayerScoreUI = {}
cfxPlayerScoreUI.version = "2.1.0"
cfxPlayerScoreUI.version = "3.0.0"
cfxPlayerScoreUI.verbose = false
--[[-- VERSION HISTORY
@ -11,6 +11,7 @@ cfxPlayerScoreUI.verbose = false
- score summary for side
- allowAll
- 2.1.1 - minor cleanup
- 3.0.0 - compatible with dynamic groups/units in DCS 2.9.6
--]]--
cfxPlayerScoreUI.requiredLibs = {
@ -19,7 +20,7 @@ cfxPlayerScoreUI.requiredLibs = {
"cfxPlayerScore",
}
cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav"
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft. stores command roots
cfxPlayerScoreUI.allowAll = true
cfxPlayerScoreUI.ranked = true

View File

@ -1,30 +1,23 @@
radioMenu = {}
radioMenu.version = "3.0.0"
radioMenu.version = "4.0.0"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
-- note: cfxMX is optional unless using types or groups attributes
radioMenu.menus = {}
radioMenu.mainMenus = {} -- dict
radioMenu.lateGroups = {} -- dict by ID
--[[--
Version History
2.1.0 - valA/valB/valC/valD attributes
OOP cfxZones
corrected CD setting for "D"
ackA, ackB, ackC, ackD attributes
valA-D now define full method, not just values
full wildcard support for ack and cooldown
2.1.1 - outMessage now works correctly
2.2.0 - clean-up
2.2.1 - corrected ackD
2.3.0 - added wildcard "*" ability for group name match
- added ackASnd .. ackDSnd sounds as options
3.0.0 - new radioMainMenu and attachTo: mechanics
cascading radioMainMenu support
detect cyclic references
detect cyclic references
4.0.0 - added infrastructure for dynamic players (DCS 2.9.6)
- added dynamic player support
--]]--
function radioMenu.addRadioMenu(theZone)
@ -53,15 +46,33 @@ end
--
-- read zone
--
function radioMenu.lateFilterPlayerForType(theUnit, theZone)
-- returns true if theUnit matches zone's group criterium
-- false otherwise
if not theUnit then return false end
local theGroup = theUnit:getGroup()
local theCat = theGroup:getCategory() -- 0 == aircraft 1 = plance
local lateGroupName = theGroup:getName()
local lateType = theUnit:getTypeName()
local allTypes = theZone.menuTypes -- {}
for idx, aType in pairs(allTypes) do
local lowerType = string.lower(aType)
if dcsCommon.stringStartsWith(lowerType, "helo") or dcsCommon.stringStartsWith(lowerType, "heli") or
aType == "helicopter" then
if theCat == 1 then return true end
elseif lowerType == "plane" or lowerType == "planes" then
if theCat == 0 then return true end
else
if aType == lateType then return true end
end
end
return false
end
function radioMenu.filterPlayerIDForType(theZone)
-- note: we currently ignore coalition
local theIDs = {}
local allTypes = {}
if dcsCommon.containsString(theZone.menuTypes, ",") then
allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
else
table.insert(allTypes, theZone.menuTypes)
end
local allTypes = theZone.menuTypes -- {}
-- now iterate all types, and include any player that matches
-- note that players may match twice, so we use a dict
@ -116,16 +127,47 @@ function radioMenu.filterPlayerIDForType(theZone)
return theIDs
end
function radioMenu.lateFilterPlayerForGroup(theUnit, theZone)
-- returns true if theUnit matches zone's group criterium
-- false otherwise. NO COA CHECK
if not theUnit then return false end
local theGroup = theUnit:getGroup()
local lateGroupName = theGroup:getName()
for idx, gName in pairs(theZone.menuGroup) do
if dcsCommon.stringEndsWith(gName, "*") then
-- we must check all group names if they start with the
-- the same root. WARNING: CASE-SENSITIVE!!!!
gName = dcsCommon.removeEnding(gName, "*")
if dcsCommon.stringStartsWith(lateGroupName, gName) then
-- group match, install menu
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: WILDCARD Player Group <" .. gName .. "*> matched with <" .. mxName .. ">: gID = <" .. gID .. ">", 30)
end
return true
end
else
if lateGroupName == gName then
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: Player Group <" .. gName .. "> found: <" .. gID .. ">", 30)
end
return true
end
end
end
return false
end
function radioMenu.lateFilterCoaForUnit(theUnit, theZone)
if theZone.coalition == 0 then return true end
if theZone.coalition == theUnit:getCoalition() then return true end
return false
end
function radioMenu.filterPlayerIDForGroup(theZone)
-- create an iterable list of groups, separated by commas
-- note that we could introduce wildcards for groups later
local theIDs = {}
local allGroups = {}
if dcsCommon.containsString(theZone.menuGroup, ",") then
allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
else
table.insert(allGroups, theZone.menuGroup)
end
local allGroups = theZone.menuGroup
for idx, gName in pairs(allGroups) do
-- if gName ends in wildcard "*" we process differently
@ -162,6 +204,45 @@ function radioMenu.filterPlayerIDForGroup(theZone)
return theIDs
end
function radioMenu.lateInstallMenu(theUnit, theZone)
-- we only add the group-individual menus (type/group).
-- all higher-level menus have been installed already
local theGroup = theUnit:getGroup()
local grp = theGroup:getID()
local gName = theGroup:getName()
radioMenu.lateGroups[grp] = gName
if not theZone.rootMenu then theZone.rootMenu = {} end
if theZone.attachTo then
local mainMenu = theZone.attachTo
theZone.mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
if theZone.menuGroup or theZone.menuTypes then
-- install menu, drop through to below
else
--trigger.action.outText("late-skipped menu for <" .. theZone.name .. ">, no group or type dependency", 30)
return
end
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, theZone.mainRoot)
theZone.rootMenu[grp] = aRoot
theZone.mcdA[grp] = 0
theZone.mcdB[grp] = 0
theZone.mcdC[grp] = 0
theZone.mcdD[grp] = 0
if theZone.itemA then
theZone.menuA[grp] = missionCommands.addCommandForGroup(grp, theZone.itemA, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "A", grp})
end
if theZone.itemB then
theZone.menuB[grp] = missionCommands.addCommandForGroup(grp, theZone.itemB, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "B", grp})
end
if theZone.itemC then
theZone.menuC[grp] = missionCommands.addCommandForGroup(grp, theZone.itemC, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "C", grp})
end
if theZone.itemD then
theZone.menuD[grp] = missionCommands.addCommandForGroup(grp, theZone.itemD, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "D", grp})
end
--trigger.action.outText("completed late-add menu for <" .. theZone.name .. "> to <" .. theUnit:getName() .. ">", 30)
end
function radioMenu.installMenu(theZone)
local gID = nil
if theZone.menuGroup then
@ -220,8 +301,8 @@ function radioMenu.installMenu(theZone)
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, theZone.mainRoot)
end
if theZone:hasProperty("itemA") then
local menuA = theZone:getStringFromZoneProperty("itemA", "<no A submenu>")
if theZone.itemA then -- :hasProperty("itemA") then
local menuA = theZone.itemA -- theZone:getStringFromZoneProperty("itemA", "<no A submenu>")
if theZone.menuGroup or theZone.menuTypes then
theZone.menuA = {}
for idx, grp in pairs(gID) do
@ -234,8 +315,8 @@ function radioMenu.installMenu(theZone)
end
end
if theZone:hasProperty("itemB") then
local menuB = theZone:getStringFromZoneProperty("itemB", "<no B submenu>")
if theZone.itemB then --:hasProperty("itemB") then
local menuB = theZone.itemB -- :getStringFromZoneProperty("itemB", "<no B submenu>")
if theZone.menuGroup or theZone.menuTypes then
theZone.menuB = {}
for idx, grp in pairs(gID) do
@ -248,8 +329,8 @@ function radioMenu.installMenu(theZone)
end
end
if theZone:hasProperty("itemC") then
local menuC = theZone:getStringFromZoneProperty("itemC", "<no C submenu>")
if theZone.itemC then --:hasProperty("itemC") then
local menuC = theZone.itemC -- :getStringFromZoneProperty("itemC", "<no C submenu>")
if theZone.menuGroup or theZone.menuTypes then
theZone.menuC = {}
for idx, grp in pairs(gID) do
@ -262,8 +343,8 @@ function radioMenu.installMenu(theZone)
end
end
if theZone:hasProperty("itemD") then
local menuD = theZone:getStringFromZoneProperty("itemD", "<no D submenu>")
if theZone.itemD then -- :hasProperty("itemD") then
local menuD = theZone.itemD -- :getStringFromZoneProperty("itemD", "<no D submenu>")
if theZone.menuGroup or theZone.menuTypes then
theZone.menuD = {}
for idx, grp in pairs(gID) do
@ -292,6 +373,20 @@ function radioMenu.createRadioMenuWithZone(theZone)
end
end
-- read items and their stuff
if theZone:hasProperty("itemA") then
theZone.itemA = theZone:getStringFromZoneProperty("itemA")
end
if theZone:hasProperty("itemB") then
theZone.itemB = theZone:getStringFromZoneProperty("itemB")
end
if theZone:hasProperty("itemC") then
theZone.itemC = theZone:getStringFromZoneProperty("itemC")
end
if theZone:hasProperty("itemD") then
theZone.itemD = theZone:getStringFromZoneProperty("itemD")
end
theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0)
-- groups / types
if theZone:hasProperty("group") then
@ -306,6 +401,27 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
end
-- now process menugroups and create sets to improve later speed
if theZone.menuGroup then
if dcsCommon.containsString(theZone.menuGroup, ",") then
local allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
allGroups = dcsCommon.trimArray(allGroups)
theZone.menuGroup = allGroups
else
theZone.menuGroup = {theZone.menuGroup} --table.insert(allGroups, theZone.menuGroup)
end
end
if theZone.menuTypes then
if dcsCommon.containsString(theZone.menuTypes, ",") then
local allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
allTypes = dcsCommon.trimArray(allTypes)
theZone.menuTypes = allTypes
else
theZone.menuTypes = {theZone.menuTypes}
end
end
theZone.menuVisible = theZone:getBoolFromZoneProperty("menuVisible", true)
-- install menu if not hidden
@ -392,13 +508,25 @@ end
function radioMenu.getMainMenuFor(mainMenu, theZone, idx)
if not idx then idx = 0 end
if not mainMenu.rootMenu[idx] then
-- trigger.action.outText("main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">: forcing idx to 0", 30)
return mainMenu.rootMenu[0]
end
-- trigger.action.outText("good main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">", 30)
return mainMenu.rootMenu[idx]
end
function radioMenu.lateInstallMainMenu(theUnit, theZone)
local theGroup = theUnit:getGroup()
local grp = theGroup:getID()
local mainRoot = nil
if not theZone.rootMenu then theZone.rootMenu = {} end
if theZone.attachTo then
local mainMenu = theZone.attachTo
mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, mainRoot)
theZone.rootMenu[grp] = aRoot
--trigger.action.outText("menu: late-attached main menu <" .. theZone.name .. "> for unit <" .. theUnit:getName() .. ">", 30)
end
function radioMenu.installMainMenu(theZone)
local gID = nil -- set of all groups this menu applies to
if theZone.menuGroup then
@ -490,6 +618,27 @@ function radioMenu.createRadioMainMenuWithZone(theZone)
elseif theZone:hasProperty("types") then
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
end
-- now process menugroups and create sets to improve later speed
if theZone.menuGroup then
if dcsCommon.containsString(theZone.menuGroup, ",") then
local allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
allGroups = dcsCommon.trimArray(allGroups)
theZone.menuGroup = allGroups
else
theZone.menuGroup = {theZone.menuGroup} --table.insert(allGroups, theZone.menuGroup)
end
end
if theZone.menuTypes then
if dcsCommon.containsString(theZone.menuTypes, ",") then
local allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
allTypes = dcsCommon.trimArray(allTypes)
theZone.menuTypes = allTypes
else
theZone.menuTypes = {theZone.menuTypes}
end
end
-- always install this one
radioMenu.installMainMenu(theZone)
@ -523,6 +672,8 @@ function radioMenu.radioOutMsg(ack, gid, theZone)
local theMsg = ack
if (gid > 0) and cfxMX then
local gName = cfxMX.groupNamesByID[gid]
if not gName then gName = radioMenu.lateGroups[gid] end
if not gName then gName = "?*?*?" end
theMsg = theMsg:gsub("<group>", gName)
end
@ -566,9 +717,9 @@ function radioMenu.setCDByGID(cd, theZone, gID, newVal)
end
function radioMenu.doMenuX(args)
theZone = args[1]
theItemIndex = args[2] -- A, B , C .. ?
theGroup = args[3] -- can be nil or groupID
local theZone = args[1]
local theItemIndex = args[2] -- A, B , C .. ?
local theGroup = args[3] -- can be nil or groupID
if not theGroup then theGroup = 0 end
local cd = radioMenu.cdByGID(theZone.mcdA, theZone, theGroup) --theZone.mcdA
@ -688,6 +839,74 @@ function radioMenu.update()
end
end
--
-- onEvent - late dynamic spawns
--
function radioMenu.lateMenuAddForUnit(theUnit)
-- iterate all menu zones and determine if this menu is to
-- install
local uName = theUnit:getName()
for zName, theZone in pairs(radioMenu.mainMenus) do
-- determine if this applies to us, and if so, install
-- for unit. only do this if menuGroups or menuTypes present
-- all others are set on coa level or higher
--trigger.action.outText("late-proccing MAIN menu <" .. zName ..">", 30)
if theZone.menuGroup then
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
radioMenu.lateFilterPlayerForGroup(theUnit, theZone) then
radioMenu.lateInstallMainMenu(theUnit, theZone)
else
--trigger.action.outText("menu: skipped GROUP main menu <" .. zName .. "> add for <" .. uName .. ">", 30)
end
elseif theZone.menuTypes then
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
radioMenu.lateFilterPlayerForType(theUnit, theZone) then
radioMenu.lateInstallMainMenu(theUnit, theZone)
else
--trigger.action.outText("menu: skipped TYPE main menu <" .. zName .. "> add for <" .. uName .. ">", 30)
end
else
-- nothing to do, was attached for coalition or higher
--trigger.action.outText("menu: did not late-install main menu <" .. zName .. ">, no group or type restrictions", 30)
end
end
for idx, theZone in pairs(radioMenu.menus) do
-- again, does this apply to us?
--trigger.action.outText("late-proccing menu <" .. theZone.name ..">", 30)
if theZone.menuGroup then
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
radioMenu.lateFilterPlayerForGroup(theUnit, theZone) then
radioMenu.lateInstallMenu(theUnit, theZone)
else
--trigger.action.outText("menu: skipped GROUP main menu <" .. theZone.name .. "> add for <" .. uName .. ">", 30)
end
elseif theZone.menuTypes then
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
radioMenu.lateFilterPlayerForType(theUnit, theZone) then
radioMenu.lateInstallMenu(theUnit, theZone)
else
--trigger.action.outText("menu: skipped TYPE main menu <" .. theZone.name .. "> add for <" .. uName .. ">", 30)
end
else
-- nothing to do, was attached for coalition or higher
--trigger.action.outText("menu: did not late-install STD menu <" .. theZone.name .. ">, no group or type restrictions", 30)
end
end
end
function radioMenu:onEvent(event)
if not event then return end
if not event.initiator then return end
local theUnit = event.initiator
if event.id == 15 then
if not cfxMX.isDynamicPlayer(theUnit) then return end
-- we have a dynamic unit spawn
--trigger.action.outText("menu: detected dynamic spawn <" .. theUnit:getName() .. ">", 30)
radioMenu.lateMenuAddForUnit(theUnit)
end
end
--
-- Config & Start
@ -756,6 +975,9 @@ function radioMenu.start()
radioMenu.addRadioMenu(aZone) -- add to list
end
-- install late spawn detector
world.addEventHandler(radioMenu)
-- start update
radioMenu.update()

View File

@ -1,5 +1,5 @@
reaper = {}
reaper.version = "1.1.0"
reaper.version = "1.2.0"
reaper.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -22,6 +22,7 @@ VERSION HISTORY
- added FAC task
- split task generation from wp generation
- updated reaper naming, uniqueNames attribute (undocumented)
1.2.0 - support twn when present
--]]--
@ -290,12 +291,21 @@ function reaper.setTarget(theZone, theTarget, cycled)
local lp = theTarget:getPoint()
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local twnLoc = ""
if twn and towns then
local name, data, dist = twn.closestTownTo(lp)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. name .. ")"
end
local theSpot = Spot.createLaser(theZone.theUav, {0, 2, 0}, lp, theZone.code)
if theZone.doSmoke then
trigger.action.smoke(lp , theZone.smokeColor )
end
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> is tracking a <" .. theTarget:getTypeName() .. "> at " .. lat .. " " .. lon .. ", code " .. theZone.code, 30)
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> is tracking a <" .. theTarget:getTypeName() .. "> at " .. lat .. " " .. lon .. twnLoc ..", code " .. theZone.code, 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
theZone.theTarget = theTarget
if theZone.theSpot then
@ -599,14 +609,23 @@ function reaper.doDroneStatus(args)
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName()
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
local twnLoc = ""
if twn and towns then
local tname, data, dist = twn.closestTownTo(lp)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. tname .. ") "
end
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. twnLoc .. " code " .. theZone.code
else
msg = msg .. "<signal failure, please try later>"
end
done[name] = true
end
else
msg = msg .. "\n(No drones are tracking a target)\n"
msg = msg .. "\n(No drones are tracking targets)\n"
end
-- collect loitering drones
@ -661,7 +680,16 @@ function reaper.doSingleDroneStatus(theZone)
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName()
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
local twnLoc = ""
if twn and towns then
local tname, data, dist = twn.closestTownTo(lp)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. tname .. ") "
end
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. twnLoc .. " code " .. theZone.code
-- now add full group intelligence
local collector = {}
@ -886,7 +914,6 @@ function reaper.start()
timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1)
-- schedule scan and track loops
-- timer.scheduleFunction(reaper.scan, {}, timer.getTime() + 1)
timer.scheduleFunction(reaper.scanALT, {}, timer.getTime() + 1)
timer.scheduleFunction(reaper.track, {}, timer.getTime() + 1)
trigger.action.outText("reaper v " .. reaper.version .. " running.", 30)

View File

@ -1,16 +1,18 @@
cfxReconGUI = {}
cfxReconGUI.version = "1.0.0"
cfxReconGUI.version = "2.0.0"
--[[-- VERSION HISTORY
- 1.0.0 - initial version
- 2.0.0 - removed dependence on cfxPlayer
- compatible with dynamically spawning players
- cleanup
--]]--
-- find & command cfxGroundTroops-based jtacs
-- UI installed via OTHER for all groups with players
-- module based on xxxGrpUI
cfxReconGUI.groupConfig = {} -- all inited group private config data
cfxReconGUI.simpleCommands = true -- if true, f10 other invokes directly
cfxReconGUI.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
--
-- C O N F I G H A N D L I N G
-- =============================
@ -38,7 +40,6 @@ function cfxReconGUI.createDefaultConfig(theGroup)
local groupUnits = theGroup:getUnits()
conf.unit = groupUnits[1] -- WARNING: ASSUMES ONE-UNIT GROUPS
cfxReconGUI.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
@ -80,12 +81,10 @@ function cfxReconGUI.getConfigForUnit(theUnit)
return conf
end
--
--
-- M E N U H A N D L I N G
-- =========================
--
--
function cfxReconGUI.clearCommsSubmenus(conf)
if conf.myCommands then
for i=1, #conf.myCommands do
@ -96,16 +95,14 @@ end
end
function cfxReconGUI.removeCommsFromConfig(conf)
cfxReconGUI.clearCommsSubmenus(conf)
cfxReconGUI.clearCommsSubmenus(conf)
if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil
end
end
-- this only works in single-unit groups. may want to check if group
-- has disappeared
-- this only works in single-unit groups
function cfxReconGUI.removeCommsForUnit(theUnit)
if not theUnit then return end
if not theUnit:isExist() then return end
@ -121,9 +118,6 @@ function cfxReconGUI.removeCommsForGroup(theGroup)
cfxReconGUI.removeCommsFromConfig(conf)
end
--
-- set main root in F10 Other. All sub menus click into this
--
function cfxReconGUI.isEligibleForMenu(theGroup)
return true
end
@ -144,7 +138,7 @@ end
function cfxReconGUI.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
-- it begins at F10-Other, and has 'Recon' as main menu with submenus
-- as required
if not theGroup then return end
if not theGroup:isExist() then return end
@ -154,8 +148,7 @@ function cfxReconGUI.setCommsMenu(theGroup)
if not cfxReconGUI.isEligibleForMenu(theGroup) then return end
local conf = cfxReconGUI.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 ALWAYSdo this
if cfxReconGUI.simpleCommands then
-- we install directly in F-10 other
@ -190,26 +183,17 @@ function cfxReconGUI.setCommsMenu(theGroup)
return
end
-- ok, first, if we don't have an F-10 menu, create one
-- if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'Recon')
end
-- clear out existing commands
cfxReconGUI.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus
cfxReconGUI.addSubMenus(conf)
cfxReconGUI.addSubMenus(conf)
end
function cfxReconGUI.addSubMenus(conf)
-- add menu items to choose from after
-- user clickedf on MAIN MENU. In this implementation
-- they all result invoked methods
local commandTxt = "Recon"
local unitName = "bogus"
if conf.unit and conf.unit:getName()then
@ -219,32 +203,10 @@ function cfxReconGUI.addSubMenus(conf)
commandTxt = commandTxt .. "***"
end
local theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
cfxReconGUI.redirectCommandX,
{conf, "recon", unitName}
)
local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, conf.myMainMenu, cfxReconGUI.redirectCommandX, {conf, "recon", unitName})
table.insert(conf.myCommands, theCommand)
--[[--
commandTxt = "This is another important command"
theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
cfxReconGUI.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 cfxReconGUI.redirectCommandX(args)
timer.scheduleFunction(cfxReconGUI.doCommandX, args, timer.getTime() + 0.1)
end
@ -260,10 +222,8 @@ function cfxReconGUI.doCommandX(args)
trigger.action.outText("+++ reconUI: doCommand: BOGUS unitName!", 30)
end
local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
-- whenever we get here, we toggle the recon mode
local theGroup = conf.theGroup
-- when we get here, we toggle the recon mode
local theUnit = conf.unit
local message = "Scout ".. unitName .. " has stopped reporting."
local theSide = conf.coalition
@ -285,94 +245,50 @@ function cfxReconGUI.doCommandX(args)
end
end
trigger.action.outTextForCoalition(theSide, message, 30)
-- reset comms
cfxReconGUI.removeCommsForGroup(theGroup)
cfxReconGUI.setCommsMenu(theGroup)
end
--
-- G R O U P M A N A G E M E N T
--
-- 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 cfxReconGUI.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
-- ensure group data exists and is updated
local conf = cfxReconGUI.getConfigForGroup(data.group)
conf.unit = data.primeUnit
conf.unitName = conf.unit:getName() -- will break if no exist
cfxReconGUI.setCommsMenu(data.group)
-- trigger.action.outText("+++ groupUI: added " .. theUnit:getName() .. " to comms menu", 30)
return
function cfxReconGUI:onEvent(theEvent)
if not theEvent then return end
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not Unit.isExist(theUnit) then return end
if not theUnit.getName then return end
if not theUnit.getGroup then return end
if not theUnit.getPlayerName then return end
if not theUnit:getPlayerName() then return end
local theGroup = theUnit:getGroup()
if theEvent.id == 15 then
-- BIRTH EVENT PLAYER
local conf = cfxReconGUI.getConfigForGroup(theGroup)
conf.unit = theUnit --data.primeUnit
conf.unitName = theUnit:getName()
cfxReconGUI.setCommsMenu(theGroup)
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 = cfxReconGUI.getConfigByGroupName(data.name)
if conf then
cfxReconGUI.removeCommsFromConfig(conf) -- remove menus
cfxReconGUI.resetConfig(conf) -- re-init this group for when it re-appears
else
trigger.action.outText("+++ reconUI: 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
end
--
-- Start
--
function cfxReconGUI.start()
-- 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
cfxReconGUI.setCommsMenuForUnit(theUnit) -- set up
-- lib check
if not dcsCommon.libCheck("cfx Recon Mode",
cfxReconMode.requiredLibs) then
return false
end
-- now install the new group notifier to install Assault Troops menu
cfxPlayer.addMonitor(cfxReconGUI.playerChangeEvent)
-- iterate existing groups so we have a start situation
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for idx, theUnit in pairs(allPlayerUnits) do
cfxReconGUI.setCommsMenuForUnit(theUnit)
end
world.addEventHandler(cfxReconGUI)
trigger.action.outText("cf/x cfxReconGUI v" .. cfxReconGUI.version .. " started", 30)
return true
end
--

View File

@ -1,5 +1,5 @@
cfxReconMode = {}
cfxReconMode.version = "2.2.2"
cfxReconMode.version = "2.3.0"
cfxReconMode.verbose = false -- set to true for debug info
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
@ -60,7 +60,8 @@ VERSION HISTORY
2.2.1 - fixed "cfxReconSMode" typo
2.2.2 - added groupNames attribute
- clean-up
2.3.0 - support for towns/twn when present
--]]--
cfxReconMode.detectionMinRange = 3000 -- meters at ground level
@ -394,6 +395,18 @@ function cfxReconMode.getLocation(theGroup)
lat, lon = dcsCommon.latLon2Text(lat, lon)
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele ..units
end
if twn and towns then
units = "km"
local village, data, dist = twn.closestTownTo(currPoint)
if cfxReconMode.imperialUnits then
dist = dist * 0.539957 -- nm conversion
units = "nm"
end
dist = math.floor(dist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(currPoint, data.p)
msg = msg .. ", " .. dist .. units .. " " .. bear .. " of " .. village
end
return msg
end

View File

@ -1,5 +1,5 @@
scribe = {}
scribe.version = "2.0.0"
scribe.version = "2.0.2"
scribe.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -13,10 +13,15 @@ VERSION HISTORY
1.0.1 postponed land, postponed takeoff, unit_lost
1.1.0 supports persistence's SHARED ability to share data across missions
2.0.0 support for main menu
2.0.1 Hardening for DCS Jul 11 patch issues
2.0.2 Secondary landing events correction
support for DCS dynamic player spawns
--]]--
scribe.verbose = true
scribe.db = {} -- indexed by player name
scribe.playerUnits = {} -- indexed by unit name. for crash detection
scribe.dynamicPlayers = {}
--[[--
unitEntry:
@ -157,12 +162,22 @@ end
-- Event handling
--
function scribe.playerBirthedIn(playerName, theUnit)
-- access db
local theEntry = scribe.getPlayerNamed(playerName) -- can be new
local myType = theUnit:getTypeName()
local uName = theUnit:getName()
local theGroup = theUnit:getGroup()
local gID = theGroup:getID()
-- install menu if dynamic plane and not defined already
if cfxMX.isDynamicPlayer(theUnit) then
local gName = theGroup:getName()
if not scribe.dynamicPlayers[gName] then
scribe.installDynamicPlayerMenu(theUnit)
scribe.dynamicPlayers[gName] = true
end
end
-- access db
local theEntry = scribe.getPlayerNamed(playerName) -- can be new
-- check if this player is still active
if theEntry.isActive then
-- do something to remedy this
@ -281,15 +296,15 @@ function scribe.playerLanded(playerName)
-- see if last landing is at least xx seconds old
local now = timer.getTime()
delta = now - uEntry.lastLanding
if delta > scribe.landingCD or delta < 0 then
if delta > scribe.landingCD then -- or delta < 0 then
uEntry.landings = uEntry.landings + 1
-- trigger.action.outText("+++scrb: added landing for " .. playerName .. ", delta is <" .. delta .. ">.", 30)
else
if scribe.verbose then
trigger.action.outText("+++scb: landing ignored: cooldown active", 30)
end
end
uEntry.lastLanding = now
end
function scribe.playerDeparted(playerName)
@ -342,6 +357,7 @@ function scribe:onEvent(theEvent)
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not theUnit then return end
if not theUnit.getName then return end -- DCS bug hardening
local uName = theUnit:getName()
if scribe.playerUnits[uName] and scribe.verbose then
trigger.action.outText("+++scb: event <" .. theEvent.id .. " = " .. dcsCommon.event2text(theEvent.id) .. ">, concerns player unit named <" .. uName .. ">.", 30)
@ -362,10 +378,13 @@ function scribe:onEvent(theEvent)
return
end
-- when we get here we have a player event
-- trigger.action.outText("+++scrb: player event <" .. theEvent.id .. ">", 30)
-- players can only ever activate by birth event
if theEvent.id == 15 then -- birth
scribe.playerBirthedIn(playerName, theUnit)
if theEvent.id == 15
or theEvent == 20
then -- birth / enter unit
-- trigger.action.outText("+++scrb: player <" .. playerName .. "> entered unit.", 30)
scribe.playerBirthedIn(playerName, theUnit) -- reset timer for landings / take-off
scribe.playerUnits[uName] = playerName -- for crash helo detection
end
@ -384,12 +403,12 @@ function scribe:onEvent(theEvent)
end
if theEvent.id == 4 or -- landed
theEvent.id == 56 then
theEvent.id == 55 then -- corrected to 55
scribe.playerLanded(playerName)
end
if theEvent.id == 3 or -- take-off
theEvent.id == 55 then -- postponed take-off
theEvent.id == 54 then -- postponed take-off, corrected to 54
scribe.playerDeparted(playerName)
-- trigger.action.outText("departure detected", 30)
end
@ -484,6 +503,31 @@ end
--
-- start
--
function scribe.installDynamicPlayerMenu(theUnit)
local mainMenu = nil
if scribe.mainMenu then
mainMenu = radioMenu.getMainMenuFor(scribe.mainMenu) -- nilling both next params will return menus[0]
end
local unitInfo = {}
local theGroup = theUnit:getGroup()
local coa = theGroup:getCoalition()
local theType = theUnit:getTypeName()
local gName = theGroup:getName()
local uName = theUnit:getName()
if scribe.verbose then
trigger.action.outText("DYNAMIC unit <" .. uName .. ">: type <" .. theType .. "> coa <" .. coa .. ">, group <" .. gName .. ">", 30)
end
unitInfo.uName = uName -- needed for reverse-lookup
unitInfo.gName = gName -- also needed for reverse lookup
unitInfo.coa = coa
unitInfo.gID = theGroup:getID()
unitInfo.uID = theUnit:getID()
unitInfo.theType = theType
-- unitInfo.cat = cfxMX.groupTypeByName[gName]
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu, mainMenu)
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
end
function scribe.startPlayerGUI()
-- scan all mx players
-- note: currently assumes single-player groups
@ -514,7 +558,7 @@ function scribe.startPlayerGUI()
unitInfo.gID = gData.groupId
unitInfo.uID = uData.unitId
unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName]
-- unitInfo.cat = cfxMX.groupTypeByName[gName]
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu, mainMenu)
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
end

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {}
cfxSpawnZones.version = "2.0.3"
cfxSpawnZones.version = "2.1.0"
cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
@ -29,6 +29,8 @@ cfxSpawnZones.spawnedGroups = {}
2.0.1 - fix in verifySpawnOwnership() when not master zone found
2.0.2 - new "moveFormation" attribute
2.0.3 - corrected type in spawnUnits? attribute
2.1.0 - masterOwner update for dmlZones.
since spawners don't extend zones, this is still old-school
--]]--
@ -111,7 +113,17 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.country = inZone:getNumberFromZoneProperty("country", 0)
if inZone:hasProperty("masterOwner") then
theSpawner.masterZoneName = inZone:getStringFromZoneProperty("masterOwner", "")
if theSpawner.masterZoneName == "" then theSpawner.masterZoneName = nil end
if theSpawner.masterZoneName == "" then
theSpawner.masterZoneName = nil
else
local masterZone = cfxZones.getZoneByName(theSpawner.masterZoneName)
if not masterZone then
trigger.action.outText("spawner " .. theSpawner.name .. " DID NOT FIND MASTER ZONE <" .. theSpawner.masterZoneName .. ">", 30)
theSpawner.masterZoneName = nil
else
theSpawner.masterZone = masterZone
end
end
end
theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country)
@ -237,18 +249,18 @@ function cfxSpawnZones.verifySpawnOwnership(spawner)
return true
end -- no master owner, all ok
local myCoalition = spawner.rawOwner
local masterZone = cfxZones.getZoneByName(spawner.masterZoneName)
if not masterZone then
trigger.action.outText("spawner " .. spawner.name .. " DID NOT FIND MASTER ZONE <" .. spawner.masterZoneName .. ">", 30)
return false
end
if not masterZone.owner then
-- local masterZone = cfxZones.getZoneByName(spawner.masterZoneName)
-- if not masterZone then
-- trigger.action.outText("spawner " .. spawner.name .. " DID NOT FIND MASTER ZONE <" .. spawner.masterZoneName .. ">", 30)
-- return false
-- end
local masterZone = spawner.masterZone
-- if not masterZone.owner then
--trigger.action.outText("spawner " .. spawner.name .. " - masterZone " .. masterZone.name .. " HAS NO OWNER????", 30)
return true
end
-- return true
-- end
if (myCoalition ~= masterZone.owner) then
if (myCoalition ~= masterZone:getCoalition()) then
-- can't spawn, surrounding area owned by enemy
return false
end

View File

@ -1,5 +1,5 @@
stopGap = {}
stopGap.version = "1.1.1 STANDALONE"
stopGap.version = "1.2.0 STANDALONE"
stopGap.verbose = false
stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg"
@ -8,6 +8,7 @@ stopGap.isMP = false
stopGap.running = true
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-entry issues
stopGap.allNeutral = false -- make all stand-ins neutral
--[[--
Written and (c) 2023 by Christian Franz
@ -37,6 +38,8 @@ stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-
1.0.9 - optimization when turning on stopgap
1.1.0 - kickTheDead option
1.1.1 - filter "from runway" clients
1.2.0 - compatibility with DCS dynamic spawns
--]]--
@ -115,8 +118,12 @@ function stopGap.staticMXFromUnitMX(theGroup, theUnit)
theStatic.heading = theUnit.heading -- may need some attention
theStatic.type = theUnit.type
theStatic.name = theUnit.name -- will magically be replaced with player unit
theStatic.payload = theUnit.payload -- not supported (yet) by DCS
theStatic.onboard_num = theUnit.onboard_num -- not supported
theStatic.cty = cfxMX.countryByName[theGroup.name]
if stopGap.allNeutral then
theStatic.cty = 82 -- UN Peache keepers, assign to neutral
end
return theStatic
end

View File

@ -1,5 +1,5 @@
stopGap = {}
stopGap.version = "1.1.2"
stopGap.version = "1.2.0"
stopGap.verbose = false
stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg"
@ -52,6 +52,9 @@ stopGap.requiredLibs = {
1.1.0 - kickTheDead option
1.1.1 - filter "from runway" clients
1.1.2 - allNeutral (DML only)
1.2.0 - DCS dynamic player spawn compatibility
stopGaps only works with MX data, so we are good, no changes
required
--]]--
@ -77,6 +80,8 @@ function stopGap.staticMXFromUnitMX(theGroup, theUnit)
theStatic.type = theUnit.type
theStatic.name = theUnit.name -- will magically be replaced with player unit
theStatic.cty = cfxMX.countryByName[theGroup.name]
theStatic.payload = theUnit.payload -- not supported (yet) by DCS
theStatic.onboard_num = theUnit.onboard_num -- not supported
-- DML only: allNeutral
if stopGap.allNeutral then
theStatic.cty = dcsCommon.getACountryForCoalition(0)
@ -499,6 +504,4 @@ if not stopGap.start() then
trigger.action.outText("+++ aborted stopGap v" .. stopGap.version .. " -- startup failed", 30)
stopGap = nil
end
--[[-- TODO
- allNeutral: spawn all player aircraft as neutral
--]]--

76
modules/twn.lua Normal file
View File

@ -0,0 +1,76 @@
twn = {}
twn.version = "1.0.1"
twn.verbose = false
--[[--
A DML unicorn - doesn't require any other scripts to function
(C) 2024 by Christian Franz
VERSION HISTORY
1.0.0 - Initial version
1.0.1 - Sinai // SinaiMap switcharoo
--]]--
function twn.closestTownTo(p) -- returns name, data, distance
if not towns then
trigger.action.outText("+++twn: Towns undefined", 30)
return nil, nil, nil
end
local closest = nil
local theName = nil
local smallest = math.huge
local x = p.x
local z = p.z
for name, entry in pairs(towns) do
local dx = x - entry.p.x
local dz = z - entry.p.z
local d = dx * dx + dz * dz -- no need to take square root
if d < smallest then
smallest = d
closest = entry
theName = name
end
end
return theName, closest, smallest^0.5 -- root it!
end
function twn.start()
-- get my theater
local theater = env.mission.theatre
-- map naming oddities
if theater == "SinaiMap" then theater = "Sinai" end
if twn.verbose then
trigger.action.outText("theater is <" .. theater .. ">", 30)
end
local path = "./Mods/terrains/" .. theater .. "/map/towns.lua"
-- assemble command
local command = 't = loadfile("' .. path .. '"); if t then t(); return net.lua2json(towns); else return nil end'
if twn.verbose then
trigger.action.outText("will run command <" .. command .. ">", 30)
end
local json = net.dostring_in("gui", command)
if json then
towns = {}
traw = net.json2lua(json)
local count = 0
for name, entry in pairs (traw) do
local p = coord.LLtoLO(entry.latitude, entry.longitude,0)
entry.p = p
towns[name] = entry
count = count + 1
end
if twn.verbose then
trigger.action.outText("+++twn: <" .. count .. "> town records processed", 30)
end
else
trigger.action.outText("+++twn: no towns accessible.", 30)
return false
end
trigger.action.outText("twn (towns importer) v " .. twn.version .. " started.", 30)
return true
end
twn.start()

View File

@ -1,5 +1,5 @@
valet = {}
valet.version = "1.1.0"
valet.version = "1.1.1"
valet.verbose = false
valet.requiredLibs = {
"dcsCommon", -- always
@ -14,6 +14,7 @@ valet.valets = {}
1.0.2 - also scan birth events
1.0.3 - outSoundFile now working correctly
1.1.0 - hysteresis is now time-based (10 seconds)
1.1.1 - hardening against DCS July-11 update issues
--]]--
function valet.addValet(theZone)
@ -361,6 +362,7 @@ function valet.checkPlayerSpawn(playerName, theUnit)
-- see if player spawned in a valet zone
if not playerName then return end
if not theUnit then return end
if not theUnit.getName then return end
local pos = theUnit:getPoint()
--trigger.action.outText("+++valet: spawn event", 30)

View File

@ -1,5 +1,5 @@
williePete = {}
williePete.version = "2.0.5"
williePete.version = "2.1.0"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters
@ -22,6 +22,8 @@ williePete.requiredLibs = {
2.0.3 - further hardened playerUpdate()
2.0.4 - support for the Kiowa's Hydra M259
2.0.5 - support for Mirage F1 WP that differ from Gazelle (?)
2.0.6 - DCS Update 7-11 2024 weapon name bug
2.1.0 - DCS update 2.9.6 dynamic spawn support
--]]--
williePete.willies = {}
@ -134,6 +136,53 @@ end
--
-- PLAYER MANAGEMENT
--
function williePete.latePlayerGUI(theUnit)
local unitInfo = {}
unitInfo.name = theUnit:getName() -- needed for reverse-lookup
local theGroup = theUnit:getGroup()
unitInfo.gName = theGroup:getName() -- gName -- also needed for reverse lookup
unitInfo.coa = theGroup:getCoalition() -- coa
unitInfo.gID = theGroup:getID() -- gData.groupId
unitInfo.uID = theUnit:getID() -- uData.unitId
unitInfo.theType = theUnit:getTypeName() -- theType
cat = theGroup:getCategory()
if cat == 0 then unitInfo.cat = "plane"
elseif cat == 1 then unitInfo.cat = "helicopter"
else
return -- whatever player is controlling, it's not for WP
end
williePete.doGUIforUnitInfo(unitInfo)
end
function williePete.doGUIforUnitInfo(unitInfo)
local pass = false
local uName = unitInfo.name
local gName = unitInfo.gName
for idx, aType in pairs(williePete.facTypes) do
if aType == "ALL" then pass = true end
if aType == "ANY" then pass = true end
if aType == theType then pass = true end
if dcsCommon.stringStartsWith(aType, "HEL") and unitInfo.cat == "helicopter" then pass = true end
if dcsCommon.stringStartsWith(aType, "PLAN") and unitInfo.cat == "plane" then pass = true end
end
if pass then -- we install a menu for this group
-- we may not want check in stuff, but it could be cool
if williePete.playerGUIs[gName] then
trigger.action.outText("+++WP: Warning: we already have WP menu for unit <" .. uName .. "> in group <" .. gName .. ">. Skipped.", 30)
elseif williePete.groupGUIs[gName] then
trigger.action.outText("+++WP: Warning: POSSIBLE MULTI-PLAYER UNIT GROUP DETECTED. We already have WP menu for Player Group <" .. gName .. ">. Skipped, only first unit supported. ", 30)
else
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
williePete.groupGUIs[gName] = unitInfo
williePete.playerGUIs[gName] = unitInfo
end
end
end
function williePete.startPlayerGUI()
-- scan all mx players
-- note: currently assumes single-player groups
@ -159,7 +208,7 @@ function williePete.startPlayerGUI()
unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName]
-- now check type against willie pete config for allowable types
local pass = false
--[[-- local pass = false
for idx, aType in pairs(williePete.facTypes) do
if aType == "ALL" then pass = true end
if aType == "ANY" then pass = true end
@ -181,7 +230,8 @@ function williePete.startPlayerGUI()
williePete.playerGUIs[gName] = unitInfo
end
end
--]]--
williePete.doGUIforUnitInfo(unitInfo)
-- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups
--williePete.playerGUIs[uName] = unitInfo
end
@ -467,7 +517,7 @@ end
function williePete.zedsDead(theObject)
if not theObject then return end
if not theObject.getName then return end -- DCS July-11 oddity.
local theName = theObject:getName()
-- now check if it's a registered blasted object:getSampleRate()
-- in multi-unit player groups, this can can lead to
@ -493,6 +543,14 @@ function williePete:onEvent(event)
return
end
-- see if a dynamic spawn
if event.id == 15 then
local theUnit = event.initiator
if not cfxMX.isDynamicPlayer(theUnit) then return end
williePete.latePlayerGUI(theUnit)
return
end
-- check if it's a dead event
if event.id == 8 then
-- death event