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 = {}
FARPZones.version = "2.1.1" FARPZones.version = "2.2.0"
FARPZones.verbose = false FARPZones.verbose = false
--[[-- --[[--
Version History Version History
@@ -24,7 +24,8 @@ FARPZones.verbose = false
2.0.2 - clean-up 2.0.2 - clean-up
verbosity enhancements verbosity enhancements
2.1.0 - integration with camp: needs repairs, produceResourceVehicles() 2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
2.1.1 - loading a farp from data respaws all defenders and resource vehicles 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) function FARPZones.produceVehicles(theFarp)
local theZone = theFarp.zone 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 -- abort production if farp is owned by neutral and
-- neutralproduction is false -- neutralproduction is false
if theFarp.owner == 0 and not theFarp.neutralProduction then if theFarp.owner == 0 and not theFarp.neutralProduction then
@@ -382,18 +381,7 @@ function FARPZones.produceVehicles(theFarp)
-- spawn resource vehicles -- spawn resource vehicles
FARPZones.produceResourceVehicles(theFarp, theCoalition) 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 -- update unique counter
theFarp.count = theFarp.count + 1 theFarp.count = theFarp.count + 1
end end
@@ -508,7 +496,6 @@ function FARPZones.saveData()
-- iterate all farp data and put them into a container each -- iterate all farp data and put them into a container each
for theZone, theFARP in pairs(FARPZones.allFARPZones) do for theZone, theFARP in pairs(FARPZones.allFARPZones) do
fName = theZone.name fName = theZone.name
--trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
local fData = {} local fData = {}
fData.owner = theFARP.owner fData.owner = theFARP.owner
fData.defenderData = dcsCommon.clone(theFARP.defenderData) fData.defenderData = dcsCommon.clone(theFARP.defenderData)
@@ -528,6 +515,17 @@ function FARPZones.saveData()
return theData return theData
end 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() function FARPZones.loadMission()
local theData = persistence.getSavedDataForModule("FARPZones") local theData = persistence.getSavedDataForModule("FARPZones")
if not theData then if not theData then
@@ -548,27 +546,9 @@ function FARPZones.loadMission()
theAB:setCoalition(theFARP.owner) -- FARP is in lockup. theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
theFARP.defenderData = dcsCommon.clone(fData.defenderData) theFARP.defenderData = dcsCommon.clone(fData.defenderData)
--[[--
local groupData = fData.defenderData
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.produceVehicles(theFARP) -- do full defender and resource cycle
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map FARPZones.drawFARPCircleInMap(theFARP) -- mark in map
-- if (not theFARP.defenders) and (not theFARP.resources) then
-- we instigate a resource and defender drop
-- FARPZones.produceVehicles(theFARP)
-- end
else else
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30) trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end end
@@ -580,10 +560,8 @@ end
-- Start -- Start
-- --
function FARPZones.releaseFARPS() function FARPZones.releaseFARPS()
-- trigger.action.outText("Releasing hold on FARPS", 30)
for idx, aFarp in pairs(FARPZones.lockup) do for idx, aFarp in pairs(FARPZones.lockup) do
aFarp:autoCapture(true) aFarp:autoCapture(true)
-- trigger.action.outText("releasing farp <" .. aFarp:getName() .. ">", 30)
end end
end end
@@ -592,13 +570,10 @@ function FARPZones.readConfig()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("farpZonesConfig") theZone = cfxZones.createSimpleZone("farpZonesConfig")
end end
FARPZones.verbose = theZone.verbose FARPZones.verbose = theZone.verbose
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30) FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1) FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1)
end end
@@ -635,8 +610,6 @@ function FARPZones.start()
for k, aZone in pairs(theZones) do for k, aZone in pairs(theZones) do
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
FARPZones.addFARPZone(aFARP) -- add to managed zones 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 if FARPZones.verbose then
trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30) trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30)
end end
@@ -656,6 +629,7 @@ function FARPZones.start()
FARPZones.startingUp = false -- not needed / read anywhere FARPZones.startingUp = false -- not needed / read anywhere
timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5) timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5)
timer.scheduleFunction(FARPZones.delayedSSB, {}, timer.getTime() + 10)
if FARPZones.refresh > 0 then if FARPZones.refresh > 0 then
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh) timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)

View File

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

View File

@@ -1,5 +1,5 @@
cfxSSBClient = {} cfxSSBClient = {}
cfxSSBClient.version = "4.0.1" cfxSSBClient.version = "5.0.0"
cfxSSBClient.verbose = false cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick -- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@@ -8,7 +8,7 @@ cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
cfxSSBClient.requiredLibs = { cfxSSBClient.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxMX", --"cfxGroups", -- for slot access "cfxMX", -- for ME Player data access
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
@@ -17,7 +17,13 @@ Version History
4.0.0 - dmlZones 4.0.0 - dmlZones
- cfxMX instead of cfxGroups - cfxMX instead of cfxGroups
4.0.1 - check slot availability immediately upon start 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 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 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 -- setting this to true only makes sense if you plan to bind in-air starts to airfields
cfxSSBClient.playerGroups = {} cfxSSBClient.playerGroups = {} -- indexed by groupName. group data with .airfield attribute
cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts cfxSSBClient.closedAirfields = {} -- list that closes airfields for all aircrafts
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying 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.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.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 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) function cfxSSBClient.setSlotAccessForGroup(theGroup)
if not theGroup then return end if not theGroup then return end
-- WARNING: theGroup is cfxGroup record -- WARNING: theGroup is cfxGroup record
-- amended for dynamic groups
local theName = theGroup.name local theName = theGroup.name
-- we now check if any plane of that group is still -- 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. -- we now iterate all playerUnits in theGroup.
-- theGroup is cfxGroup -- theGroup is cfxGroup
for idx, playerData in pairs (theGroup.playerUnits) do if theGroup.playerUnits then
local uName = playerData.name -- this is a ME unit
if cfxSSBClient.occupiedUnits[uName] then 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 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 end
return return
end end
end end
-- when we get here, no unit in the entire group is occupied -- when we get here, no unit in the entire group is occupied
local theMatchingAirfield = theGroup.airfield local theMatchingAirfield = theGroup.airfield
@@ -230,8 +249,6 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end end
trigger.action.setUserFlag(theName, blockState) trigger.action.setUserFlag(theName, blockState)
cfxSSBClient.slotState[theName] = blockState cfxSSBClient.slotState[theName] = blockState
--if cfxSSBClient.verbose then
--end
else else
if cfxSSBClient.verbose then if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30) trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30)
@@ -252,18 +269,22 @@ function cfxSSBClient.setSlotAccessForUnit(theUnit) -- calls setSlotAccessForGro
end end
function cfxSSBClient.getPlayerGroupForGroupNamed(aName) function cfxSSBClient.getPlayerGroupForGroupNamed(aName)
--is now indexed !!
return cfxSSBClient.playerGroups[aName]
--[[--
local pGroups = cfxSSBClient.playerGroups local pGroups = cfxSSBClient.playerGroups
for idx, theGroup in pairs(pGroups) do for idx, theGroup in pairs(pGroups) do
if theGroup.name == aName then return theGroup end if theGroup.name == aName then return theGroup end
end end
return nil return nil
--]]--
end end
function cfxSSBClient.setSlotAccessByAirfieldOwner() function cfxSSBClient.setSlotAccessByAirfieldOwner()
-- get all groups that have a player-controlled aircraft -- get all groups that have a player-controlled aircraft
-- now uses cached, reduced set of player planes -- now uses cached, reduced set of player planes
local pGroups = cfxSSBClient.playerGroups local pGroups = cfxSSBClient.playerGroups -- indexed by name
for idx, theGroup in pairs(pGroups) do for gName, theGroup in pairs(pGroups) do
cfxSSBClient.setSlotAccessForGroup(theGroup) cfxSSBClient.setSlotAccessForGroup(theGroup)
end end
end end
@@ -294,14 +315,8 @@ function cfxSSBClient:onEvent(event)
end end
return return
end end
local curH = theUnit:getLife() if not theUnit.getName then return end -- WTF???
local maxH = theUnit:getLife0()
local uName = theUnit:getName() 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.occupiedUnits[uName] = nil -- forget I was occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
return return
@@ -317,9 +332,10 @@ function cfxSSBClient:onEvent(event)
end end
-- write down player names and planes -- 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 if not event.initiator then return end
local theUnit = event.initiator -- we know this exists local theUnit = event.initiator -- we know this exists
if not theUnit.getName then return end -- hardening
local uName = theUnit:getName() local uName = theUnit:getName()
if not uName then return end if not uName then return end
-- player entered unit? -- player entered unit?
@@ -336,6 +352,12 @@ function cfxSSBClient:onEvent(event)
end end
-- remember this unit as player controlled plane -- remember this unit as player controlled plane
-- because player and plane can easily disconnect -- 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 cfxSSBClient.playerPlanes[uName] = playerName
if cfxSSBClient.verbose then if cfxSSBClient.verbose then
trigger.action.outText("+++SSBC:SU: noted " .. playerName .. " piloting player unit " .. uName, 30) 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 event.id == 5 then -- crash PRE-processing
if not event.initiator then return end if not event.initiator then return end
local theUnit = event.initiator local theUnit = event.initiator
if not theUnit.getName then return end
local uName = theUnit:getName() local uName = theUnit:getName()
cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
-- DO NOT RETURN NOW!!! singleuse proccing follows
end end
if cfxSSBClient.singleUse and event.id == 5 then -- crash if cfxSSBClient.singleUse and event.id == 5 then -- crash
--if not event.initiator then return end --if not event.initiator then return end
local theUnit = event.initiator local theUnit = event.initiator
if not theUnit then return end
if not theUnit.getName then return end
local uName = theUnit:getName() local uName = theUnit:getName()
if not uName then return end if not uName then return end
if not theUnit.getGroup then return end
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
if not theGroup then return end if not theGroup then return end
-- see if a player plane -- see if a player plane
@@ -371,6 +398,7 @@ function cfxSSBClient:onEvent(event)
return return
end end
-- if we get here, a player-owned plane has crashed -- 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() local gName = theGroup:getName()
if not gName then return end if not gName then return end
@@ -435,27 +463,34 @@ end
-- pre-process static player data to minimize -- pre-process static player data to minimize
-- processor load on checks -- processor load on checks
function cfxSSBClient.processPlayerData() function cfxSSBClient.processSSBPlayerData()
cfxSSBClient.playerGroups = cfxMX.getPlayerGroup() --cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
local pGroups = cfxSSBClient.playerGroups local pGroups = cfxSSBClient.SSBPlayerData -- cfxSSBClient.playerGroups
local filteredPlayers = {} local filteredPlayers = {}
for idx, theGroup in pairs(pGroups) do for gName, theGroup in pairs(pGroups) do
if theGroup.airfield ~= nil or cfxSSBClient.keepInAirGroups or if theGroup.airfield ~= nil or cfxSSBClient.keepInAirGroups or
cfxSSBClient.singleUse then cfxSSBClient.singleUse then
-- only transfer groups that have airfields (or also keepInAirGroups or when single-use) -- only transfer groups that have airfields (or also keepInAirGroups or when single-use)
-- attached. Ignore the rest as they are -- attached. Ignore the rest as they are
-- always fine -- always fine
table.insert(filteredPlayers, theGroup) --table.insert(filteredPlayers, theGroup)
filteredPlayers[gName] = theGroup
end end
end end
cfxSSBClient.playerGroups = filteredPlayers cfxSSBClient.playerGroups = filteredPlayers
-- we can now relinquish SSBPlayerData
cfxSSBClient.SSBPlayerData = nil
end end
-- add airfield information to each player group -- add airfield information to each player group
-- WARNING: AMENDS/MODIFIES DATA IN MX TO CONTAIN AIRFIELDS
-- now changed to internal clones
function cfxSSBClient.processGroupData() function cfxSSBClient.processGroupData()
local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute
local processed = {}
for idx, theGroup in pairs(pGroups) do for idx, theGroup in pairs(pGroups) do
-- we always use the first player's plane as referenced -- we always use the first player's plane as referenced
local cGroup = dcsCommon.clone(theGroup)
local playerData = theGroup.playerUnits[1] local playerData = theGroup.playerUnits[1]
local theAirfield = nil local theAirfield = nil
local delta = -1 local delta = -1
@@ -471,20 +506,52 @@ function cfxSSBClient.processGroupData()
end end
if delta > cfxSSBClient.maxAirfieldRange then if delta > cfxSSBClient.maxAirfieldRange then
-- forget airfield -- forget airfield
theAirfield = nil theAirfield = nil
if cfxSSBClient.verbose then if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " unlinked - too far from airfield" , 30) trigger.action.outText("+++SSB: group: " .. theGroup.name .. " unlinked - too far from airfield" , 30)
end 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 end
theGroup.airfield = theAirfield
else else
if cfxSSBClient.verbose then if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " start option " .. action .. " does not concern SSB", 30) trigger.action.outText("+++SSB: group: " .. theGroup.name .. " start option " .. action .. " does not concern SSB", 30)
end end
end end
end end
cfxSSBClient.SSBPlayerData = processed -- remember all relevant clones for post-processing
end 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 -- read config zone
-- --
@@ -589,7 +656,7 @@ function cfxSSBClient.start()
-- process player data to minimize effort and build cache -- process player data to minimize effort and build cache
-- into cfxSSBClient.playerGroups -- into cfxSSBClient.playerGroups
cfxSSBClient.processPlayerData() cfxSSBClient.processSSBPlayerData() -- processPlayerData()
-- process ssbc zones -- process ssbc zones
-- for in-mission DML interface -- for in-mission DML interface

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
camp = {} camp = {}
camp.ups = 1 camp.ups = 1
camp.version = "1.0.2" camp.version = "1.1.0"
camp.requiredLibs = { camp.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@@ -18,6 +18,7 @@ VERSION HISTORY
- actionSound - actionSound
- output sound with communications - output sound with communications
1.0.2 - integration with FARPZones 1.0.2 - integration with FARPZones
1.1.0 - support for DCS 2.9.6 dynamic spawns
--]]-- --]]--
-- --
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS -- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
@@ -110,24 +111,55 @@ function camp.update()
end end
function camp:onEvent(theEvent) 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 end
-- --
-- Comms -- 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() function camp.processPlayers()
-- install coms stump for all players. they will be switched in/out -- install coms stump for all players. they will be switched in/out
-- whenever it is apropriate -- whenever it is apropriate
for idx, gData in pairs(cfxMX.playerGroupByName) do for idx, gData in pairs(cfxMX.playerGroupByName) do
gID = gData.groupId gID = gData.groupId
gName = gData.name gName = gData.name
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades") --[[-- local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"}) 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 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 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
end end

View File

@@ -1,5 +1,5 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "2.0.2" cfxMX.version = "2.1.0"
cfxMX.verbose = false cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
@@ -13,7 +13,12 @@ cfxMX.verbose = false
- harmonized with cfxGroups - harmonized with cfxGroups
2.0.1 - groupHotByName 2.0.1 - groupHotByName
2.0.2 - partOfGroupDataInZone(), allGroupsInZoneByData() from milHelo 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.groupNamesByID = {}
cfxMX.groupIDbyName = {} 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 cfxMX.groups = {} -- all groups indexed b yname, cfxGroups folded into cfxMX
--[[-- group objects are --[[-- group objects are
{ {
name= "", name= "",
coalition = "" (red, blue, neutral), coalition = "" (red, blue, neutral),
coanum = # (0, 1, 2 for neutral, red, blue) coanum = # (0, 1, 2 for neutral, red, blue)
category = "" (helicopter, ship, plane, vehicle, static), category = "" (helicopter, ship, plane, vehicle, static),
hasPlayer = true/false, hasPlayer = true/false,
playerUnits = {} (for each player unit in group: name, point, action) playerUnits = {} (for each player unit in group: name, point, action)
}
}
--]]-- --]]--
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end if not fetchOriginal then fetchOriginal = false end
@@ -359,11 +362,14 @@ function cfxMX.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
return false return false
end 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 theGroupsInZone = {}
local count = 0 local count = 0
for groupName, groupData in pairs(cfxMX.groupDataByName) do 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 if cfxMX.partOfGroupDataInZone(theZone, groupData.units) then
theGroupsInZone[groupName] = groupData -- DATA! work on clones! theGroupsInZone[groupName] = groupData -- DATA! work on clones!
count = count + 1 count = count + 1
@@ -375,7 +381,36 @@ function cfxMX.allGroupsInZoneByData(theZone) -- returns groups indexed by name
end end
return theGroupsInZone, count return theGroupsInZone, count
end 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() function cfxMX.start()
cfxMX.createCrossReferences() cfxMX.createCrossReferences()
if cfxMX.verbose then if cfxMX.verbose then

View File

@@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.3.6" cfxZones.version = "4.4.2"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@@ -9,38 +9,6 @@ cfxZones.version = "4.3.6"
-- --
--[[-- VERSION HISTORY --[[-- 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.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates - 4.1.1 - evalRemainder() updates
- 4.1.2 - hash property missing warning - 4.1.2 - hash property missing warning
@@ -49,7 +17,7 @@ cfxZones.version = "4.3.6"
- small optimization for randomInRange() - small optimization for randomInRange()
- randomDelayFromPositiveRange also allows 0 - randomDelayFromPositiveRange also allows 0
- 4.3.1 - new drawText() for zones - 4.3.1 - new drawText() for zones
- dmlZones:getClosestZone() bridge - dmlZone:getClosestZone() bridge
- 4.3.2 - new getListFromZoneProperty() - 4.3.2 - new getListFromZoneProperty()
- 4.3.3 - hardened calculateZoneBounds - 4.3.3 - hardened calculateZoneBounds
- 4.3.4 - rewrote zone bounds for poly zones - 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 - 4.3.6 - tiny optimization in isPointInsideQuad
- moving zone - hardening code for static objects - moving zone - hardening code for static objects
- moving zones - now deriving dx, dy,uHeading from dcsCommon xref for linked zones - 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 return o
end 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 -- CLASSIC INTERFACE
-- --
@@ -1615,7 +1596,7 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
else else
if method ~= "on" and method ~= "f=1" then 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 end
-- default: on. -- default: on.
-- trigger.action.setUserFlag(theFlag, 1) -- trigger.action.setUserFlag(theFlag, 1)
@@ -2922,7 +2903,7 @@ function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
-- access flag -- access flag
local val = cfxZones.getFlagValue(param, theZone) local val = cfxZones.getFlagValue(param, theZone)
if not val or (val < 1) then val = 1 end 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 = msgResponses[val]
val = dcsCommon.trim(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> -- process <lat/lon/ele/mgrs/lle/latlon/alt/vel/hdg/rhdg/type/player: zone/unit>
function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses) 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 outMsg = inMsg
local uHead = 0 local uHead = 0
for idx, aLocale in pairs(locales) do for idx, aLocale in pairs(locales) do
@@ -3053,6 +3034,23 @@ function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
elseif aLocale == "rhdg" and (responses) then elseif aLocale == "rhdg" and (responses) then
local offset = cfxZones.rspMapper360(uHead, #responses) local offset = cfxZones.rspMapper360(uHead, #responses)
locString = dcsCommon.trim(responses[offset]) 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 else
-- we have mgrs -- we have mgrs
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint)) local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
@@ -3345,6 +3343,24 @@ function dmlZone:getName() -- no cfxZones.bridge!
return self.name return self.name
end 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!!!! function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really Z, don't get confused!!!!
theZone.linkedUnit = theUnit theZone.linkedUnit = theUnit
if not dx then dx = 0 end if not dx then dx = 0 end
@@ -3693,6 +3709,20 @@ function cfxZones.init()
-- much like verbose, all zones have owner -- much like verbose, all zones have owner
for n, aZone in pairs(cfxZones.zones) do for n, aZone in pairs(cfxZones.zones) do
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) 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 end
-- enable all zone's verbose flags if present -- enable all zone's verbose flags if present

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "4.0.0" csarManager.version = "4.2.1"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@@ -46,7 +46,11 @@ csarManager.ups = 1
3.4.0 - global timeLimit option in config zone 3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data - fixes expiration bug when persisting data
4.0.0 - support for mainMenu 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 AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES INTEGRATES WITH LIMITED AIRFRAMES
@@ -64,6 +68,8 @@ csarManager.requiredLibs = {
-- unitConfigs contain the config data for any helicopter -- unitConfigs contain the config data for any helicopter
-- currently in the game. The Array is indexed by unit name -- 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 = {} csarManager.unitConfigs = {}
-- --
@@ -181,7 +187,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end end
if csarManager.useRanks then if csarManager.useRanks then
-- local ranks = csarManager.ranks -- {"Lt", "Lt", "Lt", "Col", "Cpt", "WO", "WO"}
local myRank = dcsCommon.pickRandom(csarManager.ranks) local myRank = dcsCommon.pickRandom(csarManager.ranks)
name = myRank .. " " .. name name = myRank .. " " .. name
end end
@@ -243,7 +248,6 @@ function csarManager.removeMission(theMission, pickup)
if aMission ~= theMission then if aMission ~= theMission then
table.insert(newMissions, aMission) table.insert(newMissions, aMission)
else else
-- csarManager.invokeRemovedMissionCallbacks(theMission)
if pickup then if pickup then
csarManager.invokePickUpCallbacks(theMission) csarManager.invokePickUpCallbacks(theMission)
end end
@@ -296,6 +300,7 @@ end
function csarManager.getUnitConfig(theUnit) -- will create new config if not existing function csarManager.getUnitConfig(theUnit) -- will create new config if not existing
-- compatible with dynamic spawns for DCS 2.9.6
if not theUnit then if not theUnit then
trigger.action.outText("+++csar: nil unit in get config!", 30) trigger.action.outText("+++csar: nil unit in get config!", 30)
return nil return nil
@@ -331,15 +336,22 @@ function csarManager:onEvent(event)
if not dcsCommon.isPlayerUnit(theUnit) then return end -- not a player unit 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) -- 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 if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
local ID = event.id 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) csarManager.heloLanded(theUnit)
end 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) csarManager.heloDeparted(theUnit)
end end
@@ -354,18 +366,15 @@ function csarManager:onEvent(event)
csarManager.setCommsMenu(theUnit) csarManager.setCommsMenu(theUnit)
-- we also need to make sure that there are no -- we also need to make sure that there are no
-- more troopsOnBoard -- more troopsOnBoard
local myName = theUnit:getName() local myName = theUnit:getName()
local conf = csarManager.getUnitConfig(theUnit) local conf = csarManager.getUnitConfig(theUnit)
conf.unit = theUnit conf.unit = theUnit
conf.troopsOnBoard = {} conf.troopsOnBoard = {}
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
-- now also set cargo weight for the unit -- now also set cargo weight for the unit
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
totalMass = cargoSuper.calculateTotalMassFor(myName) totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
end end
end end
@@ -698,6 +707,8 @@ end
function csarManager.setCommsMenu(theUnit) 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 then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
@@ -821,11 +832,19 @@ function csarManager.doListCSARRequests(args)
status = status .. " [" .. delta .. "]" -- remove me status = status .. " [" .. delta .. "]" -- remove me
end end
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 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 else
-- leave out vectoring -- 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 end
end end

View File

@@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.0.9" dcsCommon.version = "3.1.2"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option
@@ -26,7 +26,9 @@ dcsCommon.version = "3.0.9"
3.0.9 - new getOrigPositionByID() 3.0.9 - new getOrigPositionByID()
- unitName2ID[] reverse lookup - unitName2ID[] reverse lookup
- unitName2Heading - 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 -- dcsCommon is a library of common lua functions
@@ -39,7 +41,7 @@ dcsCommon.version = "3.0.9"
-- globals -- globals
dcsCommon.cbID = 0 -- callback id for simple callback scheduling 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.coalitionSides = {0, 1, 2}
dcsCommon.maxCountry = 86 -- number of countries defined in total dcsCommon.maxCountry = 86 -- number of countries defined in total
@@ -707,7 +709,7 @@ dcsCommon.version = "3.0.9"
end end
function dcsCommon.compassPositionOfARelativeToB(A, B) 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 A then return "***error:A***" end
if not B then return "***error:B***" end if not B then return "***error:B***" end
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360 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 "Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48 "unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
"unit task timeout", "unit task stage", -- 50 "unit task timeout", "unit task stage", -- 50
"subtask score", "extra score", "mission restart", "winner", "subtask score", "mission restart", "winner", -- 53
"postponed takeoff", "postponed land", -- 56 "runway takeoff", "runway touchdown", "LMS Restart", -- 56
"max"} "sim freeze", "sum unfreeze", "player start repair", "player end repair", --60
"max",} -- 61
if id > #events then return "Unknown (ID=" .. id .. ")" end if id > #events then return "Unknown (ID=" .. id .. ")" end
return events[id] return events[id]
end end
@@ -2839,9 +2842,9 @@ end
function dcsCommon.isTroopCarrier(theUnit, carriers) function dcsCommon.isTroopCarrier(theUnit, carriers)
-- return true if conf can carry troups -- return true if conf can carry troups
if not theUnit then return false end 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 -- 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() local grp = theUnit:getGroup()
if grp:getCategory() == 1 then -- NOT category bug prone, is a group check if grp:getCategory() == 1 then -- NOT category bug prone, is a group check
return true return true

View File

@@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "3.0.4" cfxHeloTroops.version = "3.1.0"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@@ -8,31 +8,6 @@ cfxHeloTroops.requestRange = 500 -- meters
-- --
--[[-- --[[--
VERSION HISTORY 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 3.0.0 - added requestable cloner support
- harmonized spawning invocations across cloners and spawners - harmonized spawning invocations across cloners and spawners
- dmlZones - dmlZones
@@ -41,14 +16,10 @@ cfxHeloTroops.requestRange = 500 -- meters
3.0.2 - fixed a typo in in-air menu 3.0.2 - fixed a typo in in-air menu
3.0.3 - pointInZone check for insertion rather than radius 3.0.3 - pointInZone check for insertion rather than radius
3.0.4 - also handles picking up troops with orders "captureandhold" 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 = { cfxHeloTroops.requiredLibs = {
@@ -284,9 +255,8 @@ function cfxHeloTroops.addConfigMenu(conf)
end end
function cfxHeloTroops.setCommsMenu(theUnit) function cfxHeloTroops.setCommsMenu(theUnit)
-- depending on own load state, we set the command structure -- compatible with DCS 2.9.6 dynamic spawns
-- it begins at 10-other, and has 'Assault Troops' as main menu with submenus -- set F10 Other.. menu for group
-- as required
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
@@ -301,7 +271,7 @@ function cfxHeloTroops.setCommsMenu(theUnit)
local group = theUnit:getGroup() local group = theUnit:getGroup()
local id = group:getID() local id = group:getID()
local conf = cfxHeloTroops.getUnitConfig(theUnit) 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 conf.unit = theUnit -- link back
-- ok, first, if we don't have an F-10 menu, create one -- ok, first, if we don't have an F-10 menu, create one
@@ -819,10 +789,10 @@ function cfxHeloTroops:onEvent(theEvent)
local initiator = theEvent.initiator local initiator = theEvent.initiator
if not initiator then return end -- not interested if not initiator then return end -- not interested
local theUnit = initiator local theUnit = initiator
local name = theUnit:getName()
-- see if this is a player aircraft -- 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
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 -- only for helicopters -- overridedden by troop carriers
-- we don't check for cat any more, so any airframe -- we don't check for cat any more, so any airframe

View File

@@ -1,5 +1,5 @@
jtacGrpUI = {} jtacGrpUI = {}
jtacGrpUI.version = "3.0.0" jtacGrpUI.version = "3.1.0"
jtacGrpUI.requiredLibs = { jtacGrpUI.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", "cfxZones",
@@ -12,6 +12,7 @@ jtacGrpUI.requiredLibs = {
- clean-up - clean-up
- jtacSound - jtacSound
3.0.0 - support for attachTo: 3.0.0 - support for attachTo:
3.1.0 - support for DCS 2.0.6 dynamic player spwans
--]]-- --]]--
-- find & command cfxGroundTroops-based jtacs -- find & command cfxGroundTroops-based jtacs
@@ -151,7 +152,7 @@ function jtacGrpUI.setCommsMenu(theGroup)
end end
local conf = jtacGrpUI.getConfigForGroup(theGroup) 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 if jtacGrpUI.simpleCommands then
-- we install directly in F-10 other -- we install directly in F-10 other
@@ -294,25 +295,29 @@ function jtacGrpUI:onEvent(theEvent)
if not theEvent then return end if not theEvent then return end
local theUnit = theEvent.initiator local theUnit = theEvent.initiator
if not theUnit then return end if not theUnit then return end
if not theUnit.getName then return end -- dcs 2.9.6 jul-11 fix
local uName = theUnit:getName() local uName = theUnit:getName()
if not theUnit.getPlayerName then return end if not theUnit.getPlayerName then return end
if not theUnit:getPlayerName() then return end if not theUnit:getPlayerName() then return end
-- we now have a player birth event. local id = theEvent.id
local pName = theUnit:getPlayerName() if id == 15 then
local theGroup = theUnit:getGroup() -- we now have a player birth event.
if not theGroup then return end local pName = theUnit:getPlayerName()
local gName = theGroup:getName() local theGroup = theUnit:getGroup()
if not gName then return end if not theGroup then return end
if jtacGrpUI.verbose then local gName = theGroup:getName()
trigger.action.outText("+++jGUI: birth player. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30) 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 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 end

View File

@@ -1,5 +1,5 @@
messenger = {} messenger = {}
messenger.version = "3.1.0" messenger.version = "3.2.0"
messenger.verbose = false messenger.verbose = false
messenger.requiredLibs = { messenger.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@@ -13,6 +13,8 @@ messenger.messengers = {}
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger? 3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
3.1.0 - msgGroup supports multiple groups, separated by comma 3.1.0 - msgGroup supports multiple groups, separated by comma
- msgUnit supports multiple units, 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) function messenger.addMessenger(theZone)
@@ -165,7 +167,7 @@ end
-- --
-- reat attributes -- read attributes
-- --
function messenger.createMessengerWithZone(theZone) function messenger.createMessengerWithZone(theZone)
local aMessage = theZone:getStringFromZoneProperty("message", "") local aMessage = theZone:getStringFromZoneProperty("message", "")

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
cfxPlayerScoreUI = {} cfxPlayerScoreUI = {}
cfxPlayerScoreUI.version = "2.1.0" cfxPlayerScoreUI.version = "3.0.0"
cfxPlayerScoreUI.verbose = false cfxPlayerScoreUI.verbose = false
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@@ -11,6 +11,7 @@ cfxPlayerScoreUI.verbose = false
- score summary for side - score summary for side
- allowAll - allowAll
- 2.1.1 - minor cleanup - 2.1.1 - minor cleanup
- 3.0.0 - compatible with dynamic groups/units in DCS 2.9.6
--]]-- --]]--
cfxPlayerScoreUI.requiredLibs = { cfxPlayerScoreUI.requiredLibs = {
@@ -19,7 +20,7 @@ cfxPlayerScoreUI.requiredLibs = {
"cfxPlayerScore", "cfxPlayerScore",
} }
cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav" 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.allowAll = true
cfxPlayerScoreUI.ranked = true cfxPlayerScoreUI.ranked = true

View File

@@ -1,30 +1,23 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "3.0.0" radioMenu.version = "4.0.0"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
-- note: cfxMX is optional unless using types or groups attributes
radioMenu.menus = {} radioMenu.menus = {}
radioMenu.mainMenus = {} -- dict radioMenu.mainMenus = {} -- dict
radioMenu.lateGroups = {} -- dict by ID
--[[-- --[[--
Version History 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 3.0.0 - new radioMainMenu and attachTo: mechanics
cascading radioMainMenu support 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) function radioMenu.addRadioMenu(theZone)
@@ -53,15 +46,33 @@ end
-- --
-- read zone -- 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) function radioMenu.filterPlayerIDForType(theZone)
-- note: we currently ignore coalition -- note: we currently ignore coalition
local theIDs = {} local theIDs = {}
local allTypes = {} local allTypes = theZone.menuTypes -- {}
if dcsCommon.containsString(theZone.menuTypes, ",") then
allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
else
table.insert(allTypes, theZone.menuTypes)
end
-- now iterate all types, and include any player that matches -- now iterate all types, and include any player that matches
-- note that players may match twice, so we use a dict -- note that players may match twice, so we use a dict
@@ -116,16 +127,47 @@ function radioMenu.filterPlayerIDForType(theZone)
return theIDs return theIDs
end 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) function radioMenu.filterPlayerIDForGroup(theZone)
-- create an iterable list of groups, separated by commas -- create an iterable list of groups, separated by commas
-- note that we could introduce wildcards for groups later -- note that we could introduce wildcards for groups later
local theIDs = {} local theIDs = {}
local allGroups = {} local allGroups = theZone.menuGroup
if dcsCommon.containsString(theZone.menuGroup, ",") then
allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
else
table.insert(allGroups, theZone.menuGroup)
end
for idx, gName in pairs(allGroups) do for idx, gName in pairs(allGroups) do
-- if gName ends in wildcard "*" we process differently -- if gName ends in wildcard "*" we process differently
@@ -162,6 +204,45 @@ function radioMenu.filterPlayerIDForGroup(theZone)
return theIDs return theIDs
end 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) function radioMenu.installMenu(theZone)
local gID = nil local gID = nil
if theZone.menuGroup then if theZone.menuGroup then
@@ -220,8 +301,8 @@ function radioMenu.installMenu(theZone)
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, theZone.mainRoot) theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, theZone.mainRoot)
end end
if theZone:hasProperty("itemA") then if theZone.itemA then -- :hasProperty("itemA") then
local menuA = theZone:getStringFromZoneProperty("itemA", "<no A submenu>") local menuA = theZone.itemA -- theZone:getStringFromZoneProperty("itemA", "<no A submenu>")
if theZone.menuGroup or theZone.menuTypes then if theZone.menuGroup or theZone.menuTypes then
theZone.menuA = {} theZone.menuA = {}
for idx, grp in pairs(gID) do for idx, grp in pairs(gID) do
@@ -234,8 +315,8 @@ function radioMenu.installMenu(theZone)
end end
end end
if theZone:hasProperty("itemB") then if theZone.itemB then --:hasProperty("itemB") then
local menuB = theZone:getStringFromZoneProperty("itemB", "<no B submenu>") local menuB = theZone.itemB -- :getStringFromZoneProperty("itemB", "<no B submenu>")
if theZone.menuGroup or theZone.menuTypes then if theZone.menuGroup or theZone.menuTypes then
theZone.menuB = {} theZone.menuB = {}
for idx, grp in pairs(gID) do for idx, grp in pairs(gID) do
@@ -248,8 +329,8 @@ function radioMenu.installMenu(theZone)
end end
end end
if theZone:hasProperty("itemC") then if theZone.itemC then --:hasProperty("itemC") then
local menuC = theZone:getStringFromZoneProperty("itemC", "<no C submenu>") local menuC = theZone.itemC -- :getStringFromZoneProperty("itemC", "<no C submenu>")
if theZone.menuGroup or theZone.menuTypes then if theZone.menuGroup or theZone.menuTypes then
theZone.menuC = {} theZone.menuC = {}
for idx, grp in pairs(gID) do for idx, grp in pairs(gID) do
@@ -262,8 +343,8 @@ function radioMenu.installMenu(theZone)
end end
end end
if theZone:hasProperty("itemD") then if theZone.itemD then -- :hasProperty("itemD") then
local menuD = theZone:getStringFromZoneProperty("itemD", "<no D submenu>") local menuD = theZone.itemD -- :getStringFromZoneProperty("itemD", "<no D submenu>")
if theZone.menuGroup or theZone.menuTypes then if theZone.menuGroup or theZone.menuTypes then
theZone.menuD = {} theZone.menuD = {}
for idx, grp in pairs(gID) do for idx, grp in pairs(gID) do
@@ -292,6 +373,20 @@ function radioMenu.createRadioMenuWithZone(theZone)
end end
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) theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0)
-- groups / types -- groups / types
if theZone:hasProperty("group") then if theZone:hasProperty("group") then
@@ -306,6 +401,27 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none") theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
end 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) theZone.menuVisible = theZone:getBoolFromZoneProperty("menuVisible", true)
-- install menu if not hidden -- install menu if not hidden
@@ -392,13 +508,25 @@ end
function radioMenu.getMainMenuFor(mainMenu, theZone, idx) function radioMenu.getMainMenuFor(mainMenu, theZone, idx)
if not idx then idx = 0 end if not idx then idx = 0 end
if not mainMenu.rootMenu[idx] then 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] return mainMenu.rootMenu[0]
end end
-- trigger.action.outText("good main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">", 30)
return mainMenu.rootMenu[idx] return mainMenu.rootMenu[idx]
end 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) function radioMenu.installMainMenu(theZone)
local gID = nil -- set of all groups this menu applies to local gID = nil -- set of all groups this menu applies to
if theZone.menuGroup then if theZone.menuGroup then
@@ -490,6 +618,27 @@ function radioMenu.createRadioMainMenuWithZone(theZone)
elseif theZone:hasProperty("types") then elseif theZone:hasProperty("types") then
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none") theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
end 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 -- always install this one
radioMenu.installMainMenu(theZone) radioMenu.installMainMenu(theZone)
@@ -523,6 +672,8 @@ function radioMenu.radioOutMsg(ack, gid, theZone)
local theMsg = ack local theMsg = ack
if (gid > 0) and cfxMX then if (gid > 0) and cfxMX then
local gName = cfxMX.groupNamesByID[gid] 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) theMsg = theMsg:gsub("<group>", gName)
end end
@@ -566,9 +717,9 @@ function radioMenu.setCDByGID(cd, theZone, gID, newVal)
end end
function radioMenu.doMenuX(args) function radioMenu.doMenuX(args)
theZone = args[1] local theZone = args[1]
theItemIndex = args[2] -- A, B , C .. ? local theItemIndex = args[2] -- A, B , C .. ?
theGroup = args[3] -- can be nil or groupID local theGroup = args[3] -- can be nil or groupID
if not theGroup then theGroup = 0 end if not theGroup then theGroup = 0 end
local cd = radioMenu.cdByGID(theZone.mcdA, theZone, theGroup) --theZone.mcdA local cd = radioMenu.cdByGID(theZone.mcdA, theZone, theGroup) --theZone.mcdA
@@ -688,6 +839,74 @@ function radioMenu.update()
end end
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 -- Config & Start
@@ -756,6 +975,9 @@ function radioMenu.start()
radioMenu.addRadioMenu(aZone) -- add to list radioMenu.addRadioMenu(aZone) -- add to list
end end
-- install late spawn detector
world.addEventHandler(radioMenu)
-- start update -- start update
radioMenu.update() radioMenu.update()

View File

@@ -1,5 +1,5 @@
reaper = {} reaper = {}
reaper.version = "1.1.0" reaper.version = "1.2.0"
reaper.requiredLibs = { reaper.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@@ -22,6 +22,7 @@ VERSION HISTORY
- added FAC task - added FAC task
- split task generation from wp generation - split task generation from wp generation
- updated reaper naming, uniqueNames attribute (undocumented) - 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 lp = theTarget:getPoint()
local lat, lon, alt = coord.LOtoLL(lp) local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon) 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) local theSpot = Spot.createLaser(theZone.theUav, {0, 2, 0}, lp, theZone.code)
if theZone.doSmoke then if theZone.doSmoke then
trigger.action.smoke(lp , theZone.smokeColor ) trigger.action.smoke(lp , theZone.smokeColor )
end 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) trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
theZone.theTarget = theTarget theZone.theTarget = theTarget
if theZone.theSpot then if theZone.theSpot then
@@ -599,14 +609,23 @@ function reaper.doDroneStatus(args)
local lat, lon, alt = coord.LOtoLL(lp) local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon) lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName() 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 else
msg = msg .. "<signal failure, please try later>" msg = msg .. "<signal failure, please try later>"
end end
done[name] = true done[name] = true
end end
else else
msg = msg .. "\n(No drones are tracking a target)\n" msg = msg .. "\n(No drones are tracking targets)\n"
end end
-- collect loitering drones -- collect loitering drones
@@ -661,7 +680,16 @@ function reaper.doSingleDroneStatus(theZone)
local lat, lon, alt = coord.LOtoLL(lp) local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon) lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName() 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 -- now add full group intelligence
local collector = {} local collector = {}
@@ -886,7 +914,6 @@ function reaper.start()
timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1) timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1)
-- schedule scan and track loops -- schedule scan and track loops
-- timer.scheduleFunction(reaper.scan, {}, timer.getTime() + 1)
timer.scheduleFunction(reaper.scanALT, {}, timer.getTime() + 1) timer.scheduleFunction(reaper.scanALT, {}, timer.getTime() + 1)
timer.scheduleFunction(reaper.track, {}, timer.getTime() + 1) timer.scheduleFunction(reaper.track, {}, timer.getTime() + 1)
trigger.action.outText("reaper v " .. reaper.version .. " running.", 30) trigger.action.outText("reaper v " .. reaper.version .. " running.", 30)

View File

@@ -1,16 +1,18 @@
cfxReconGUI = {} cfxReconGUI = {}
cfxReconGUI.version = "1.0.0" cfxReconGUI.version = "2.0.0"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 1.0.0 - initial version - 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.groupConfig = {} -- all inited group private config data
cfxReconGUI.simpleCommands = true -- if true, f10 other invokes directly 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 -- 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() local groupUnits = theGroup:getUnits()
conf.unit = groupUnits[1] -- WARNING: ASSUMES ONE-UNIT GROUPS conf.unit = groupUnits[1] -- WARNING: ASSUMES ONE-UNIT GROUPS
cfxReconGUI.resetConfig(conf) cfxReconGUI.resetConfig(conf)
conf.mainMenu = nil; -- this is where we store the main menu if we branch conf.mainMenu = nil; -- this is where we store the main menu if we branch
conf.myCommands = nil; -- this is where we store the commands if we branch conf.myCommands = nil; -- this is where we store the commands if we branch
@@ -80,12 +81,10 @@ function cfxReconGUI.getConfigForUnit(theUnit)
return conf return conf
end end
--
-- --
-- M E N U H A N D L I N G -- M E N U H A N D L I N G
-- =========================
--
-- --
function cfxReconGUI.clearCommsSubmenus(conf) function cfxReconGUI.clearCommsSubmenus(conf)
if conf.myCommands then if conf.myCommands then
for i=1, #conf.myCommands do for i=1, #conf.myCommands do
@@ -96,16 +95,14 @@ end
end end
function cfxReconGUI.removeCommsFromConfig(conf) function cfxReconGUI.removeCommsFromConfig(conf)
cfxReconGUI.clearCommsSubmenus(conf) cfxReconGUI.clearCommsSubmenus(conf)
if conf.myMainMenu then if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu) missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil conf.myMainMenu = nil
end end
end end
-- this only works in single-unit groups. may want to check if group -- this only works in single-unit groups
-- has disappeared
function cfxReconGUI.removeCommsForUnit(theUnit) function cfxReconGUI.removeCommsForUnit(theUnit)
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
@@ -121,9 +118,6 @@ function cfxReconGUI.removeCommsForGroup(theGroup)
cfxReconGUI.removeCommsFromConfig(conf) cfxReconGUI.removeCommsFromConfig(conf)
end end
--
-- set main root in F10 Other. All sub menus click into this
--
function cfxReconGUI.isEligibleForMenu(theGroup) function cfxReconGUI.isEligibleForMenu(theGroup)
return true return true
end end
@@ -144,7 +138,7 @@ end
function cfxReconGUI.setCommsMenu(theGroup) function cfxReconGUI.setCommsMenu(theGroup)
-- depending on own load state, we set the command structure -- 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 -- as required
if not theGroup then return end if not theGroup then return end
if not theGroup:isExist() 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 if not cfxReconGUI.isEligibleForMenu(theGroup) then return end
local conf = cfxReconGUI.getConfigForGroup(theGroup) local conf = cfxReconGUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash conf.id = theGroup:getID(); -- we ALWAYSdo this
-- trigger.action.outText("+++ setting group <".. conf.theGroup:getName() .. "> jtac command", 30)
if cfxReconGUI.simpleCommands then if cfxReconGUI.simpleCommands then
-- we install directly in F-10 other -- we install directly in F-10 other
@@ -190,26 +183,17 @@ function cfxReconGUI.setCommsMenu(theGroup)
return return
end end
-- if we don't have an F-10 menu, create one
-- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'Recon') conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'Recon')
end end
-- clear out existing commands -- clear out existing commands
cfxReconGUI.clearCommsSubmenus(conf) cfxReconGUI.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus -- add our own submenus
cfxReconGUI.addSubMenus(conf) cfxReconGUI.addSubMenus(conf)
end end
function cfxReconGUI.addSubMenus(conf) 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 commandTxt = "Recon"
local unitName = "bogus" local unitName = "bogus"
if conf.unit and conf.unit:getName()then if conf.unit and conf.unit:getName()then
@@ -219,32 +203,10 @@ function cfxReconGUI.addSubMenus(conf)
commandTxt = commandTxt .. "***" commandTxt = commandTxt .. "***"
end end
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, conf.myMainMenu, cfxReconGUI.redirectCommandX, {conf, "recon", unitName})
conf.id,
commandTxt,
conf.myMainMenu,
cfxReconGUI.redirectCommandX,
{conf, "recon", unitName}
)
table.insert(conf.myCommands, theCommand) 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 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) function cfxReconGUI.redirectCommandX(args)
timer.scheduleFunction(cfxReconGUI.doCommandX, args, timer.getTime() + 0.1) timer.scheduleFunction(cfxReconGUI.doCommandX, args, timer.getTime() + 0.1)
end end
@@ -260,10 +222,8 @@ function cfxReconGUI.doCommandX(args)
trigger.action.outText("+++ reconUI: doCommand: BOGUS unitName!", 30) trigger.action.outText("+++ reconUI: doCommand: BOGUS unitName!", 30)
end end
local theGroup = conf.theGroup local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30) -- when we get here, we toggle the recon mode
-- whenever we get here, we toggle the recon mode
local theUnit = conf.unit local theUnit = conf.unit
local message = "Scout ".. unitName .. " has stopped reporting." local message = "Scout ".. unitName .. " has stopped reporting."
local theSide = conf.coalition local theSide = conf.coalition
@@ -285,94 +245,50 @@ function cfxReconGUI.doCommandX(args)
end end
end end
trigger.action.outTextForCoalition(theSide, message, 30) trigger.action.outTextForCoalition(theSide, message, 30)
-- reset comms -- reset comms
cfxReconGUI.removeCommsForGroup(theGroup) cfxReconGUI.removeCommsForGroup(theGroup)
cfxReconGUI.setCommsMenu(theGroup) cfxReconGUI.setCommsMenu(theGroup)
end end
function cfxReconGUI:onEvent(theEvent)
if not theEvent then return end
-- if not theEvent.initiator then return end
-- G R O U P M A N A G E M E N T local theUnit = theEvent.initiator
-- if not Unit.isExist(theUnit) then return end
-- Group Management is required to make sure all groups if not theUnit.getName then return end
-- receive a comms menu and that they receive a clean-up if not theUnit.getGroup then return end
-- when required if not theUnit.getPlayerName then return end
-- if not theUnit:getPlayerName() then return end
-- Callbacks are provided by cfxPlayer module to which we local theGroup = theUnit:getGroup()
-- subscribe during init if theEvent.id == 15 then
-- -- BIRTH EVENT PLAYER
function cfxReconGUI.playerChangeEvent(evType, description, player, data) local conf = cfxReconGUI.getConfigForGroup(theGroup)
--trigger.action.outText("+++ groupUI: received <".. evType .. "> Event", 30) conf.unit = theUnit --data.primeUnit
if evType == "newGroup" then conf.unitName = theUnit:getName()
-- initialized attributes are in data as follows cfxReconGUI.setCommsMenu(theGroup)
-- .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
end end
if evType == "removeGroup" then
-- data is the player record that no longer exists. it consists of
-- .name
-- we must remove the comms menu for this group else we try to add another one to this group later
local conf = 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 end
-- --
-- Start -- Start
-- --
function cfxReconGUI.start() function cfxReconGUI.start()
-- lib check
-- iterate existing groups so we have a start situation if not dcsCommon.libCheck("cfx Recon Mode",
-- now iterate through all player groups and install the Assault Troop Menu cfxReconMode.requiredLibs) then
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it! return false
-- 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
end 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) trigger.action.outText("cf/x cfxReconGUI v" .. cfxReconGUI.version .. " started", 30)
return true
end end
-- --

View File

@@ -1,5 +1,5 @@
cfxReconMode = {} cfxReconMode = {}
cfxReconMode.version = "2.2.2" cfxReconMode.version = "2.3.0"
cfxReconMode.verbose = false -- set to true for debug info 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 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.1 - fixed "cfxReconSMode" typo
2.2.2 - added groupNames attribute 2.2.2 - added groupNames attribute
- clean-up - clean-up
2.3.0 - support for towns/twn when present
--]]-- --]]--
cfxReconMode.detectionMinRange = 3000 -- meters at ground level cfxReconMode.detectionMinRange = 3000 -- meters at ground level
@@ -394,6 +395,18 @@ function cfxReconMode.getLocation(theGroup)
lat, lon = dcsCommon.latLon2Text(lat, lon) lat, lon = dcsCommon.latLon2Text(lat, lon)
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele ..units msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele ..units
end 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 return msg
end end

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
stopGap = {} stopGap = {}
stopGap.version = "1.1.1 STANDALONE" stopGap.version = "1.2.0 STANDALONE"
stopGap.verbose = false stopGap.verbose = false
stopGap.ssbEnabled = true stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg" stopGap.ignoreMe = "-sg"
@@ -8,6 +8,7 @@ stopGap.isMP = false
stopGap.running = true stopGap.running = true
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour 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.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 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.0.9 - optimization when turning on stopgap
1.1.0 - kickTheDead option 1.1.0 - kickTheDead option
1.1.1 - filter "from runway" clients 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.heading = theUnit.heading -- may need some attention
theStatic.type = theUnit.type theStatic.type = theUnit.type
theStatic.name = theUnit.name -- will magically be replaced with player unit 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] theStatic.cty = cfxMX.countryByName[theGroup.name]
if stopGap.allNeutral then
theStatic.cty = 82 -- UN Peache keepers, assign to neutral
end
return theStatic return theStatic
end end

View File

@@ -1,5 +1,5 @@
stopGap = {} stopGap = {}
stopGap.version = "1.1.2" stopGap.version = "1.2.0"
stopGap.verbose = false stopGap.verbose = false
stopGap.ssbEnabled = true stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg" stopGap.ignoreMe = "-sg"
@@ -52,6 +52,9 @@ stopGap.requiredLibs = {
1.1.0 - kickTheDead option 1.1.0 - kickTheDead option
1.1.1 - filter "from runway" clients 1.1.1 - filter "from runway" clients
1.1.2 - allNeutral (DML only) 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.type = theUnit.type
theStatic.name = theUnit.name -- will magically be replaced with player unit theStatic.name = theUnit.name -- will magically be replaced with player unit
theStatic.cty = cfxMX.countryByName[theGroup.name] 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 -- DML only: allNeutral
if stopGap.allNeutral then if stopGap.allNeutral then
theStatic.cty = dcsCommon.getACountryForCoalition(0) 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) trigger.action.outText("+++ aborted stopGap v" .. stopGap.version .. " -- startup failed", 30)
stopGap = nil stopGap = nil
end 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 = {}
valet.version = "1.1.0" valet.version = "1.1.1"
valet.verbose = false valet.verbose = false
valet.requiredLibs = { valet.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@@ -14,6 +14,7 @@ valet.valets = {}
1.0.2 - also scan birth events 1.0.2 - also scan birth events
1.0.3 - outSoundFile now working correctly 1.0.3 - outSoundFile now working correctly
1.1.0 - hysteresis is now time-based (10 seconds) 1.1.0 - hysteresis is now time-based (10 seconds)
1.1.1 - hardening against DCS July-11 update issues
--]]-- --]]--
function valet.addValet(theZone) function valet.addValet(theZone)
@@ -361,6 +362,7 @@ function valet.checkPlayerSpawn(playerName, theUnit)
-- see if player spawned in a valet zone -- see if player spawned in a valet zone
if not playerName then return end if not playerName then return end
if not theUnit then return end if not theUnit then return end
if not theUnit.getName then return end
local pos = theUnit:getPoint() local pos = theUnit:getPoint()
--trigger.action.outText("+++valet: spawn event", 30) --trigger.action.outText("+++valet: spawn event", 30)

View File

@@ -1,5 +1,5 @@
williePete = {} williePete = {}
williePete.version = "2.0.5" williePete.version = "2.1.0"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters, -- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters -- with interpolation even at 3 meters
@@ -22,6 +22,8 @@ williePete.requiredLibs = {
2.0.3 - further hardened playerUpdate() 2.0.3 - further hardened playerUpdate()
2.0.4 - support for the Kiowa's Hydra M259 2.0.4 - support for the Kiowa's Hydra M259
2.0.5 - support for Mirage F1 WP that differ from Gazelle (?) 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 = {} williePete.willies = {}
@@ -134,6 +136,53 @@ end
-- --
-- PLAYER MANAGEMENT -- 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() function williePete.startPlayerGUI()
-- scan all mx players -- scan all mx players
-- note: currently assumes single-player groups -- note: currently assumes single-player groups
@@ -159,7 +208,7 @@ function williePete.startPlayerGUI()
unitInfo.theType = theType unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName] unitInfo.cat = cfxMX.groupTypeByName[gName]
-- now check type against willie pete config for allowable types -- now check type against willie pete config for allowable types
local pass = false --[[-- local pass = false
for idx, aType in pairs(williePete.facTypes) do for idx, aType in pairs(williePete.facTypes) do
if aType == "ALL" then pass = true end if aType == "ALL" then pass = true end
if aType == "ANY" then pass = true end if aType == "ANY" then pass = true end
@@ -181,7 +230,8 @@ function williePete.startPlayerGUI()
williePete.playerGUIs[gName] = unitInfo williePete.playerGUIs[gName] = unitInfo
end end
end end
--]]--
williePete.doGUIforUnitInfo(unitInfo)
-- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups -- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups
--williePete.playerGUIs[uName] = unitInfo --williePete.playerGUIs[uName] = unitInfo
end end
@@ -467,7 +517,7 @@ end
function williePete.zedsDead(theObject) function williePete.zedsDead(theObject)
if not theObject then return end if not theObject then return end
if not theObject.getName then return end -- DCS July-11 oddity.
local theName = theObject:getName() local theName = theObject:getName()
-- now check if it's a registered blasted object:getSampleRate() -- now check if it's a registered blasted object:getSampleRate()
-- in multi-unit player groups, this can can lead to -- in multi-unit player groups, this can can lead to
@@ -493,6 +543,14 @@ function williePete:onEvent(event)
return return
end 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 -- check if it's a dead event
if event.id == 8 then if event.id == 8 then
-- death event -- death event