mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 2.3.0
DCS Jul-11, Jul-22, Aug-09 code hardening. NEW: Convoy and TWN
This commit is contained in:
parent
76083ff2b6
commit
f7a8705aa5
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
FARPZones = {}
|
||||
FARPZones.version = "2.1.1"
|
||||
FARPZones.version = "2.2.0"
|
||||
FARPZones.verbose = false
|
||||
--[[--
|
||||
Version History
|
||||
@ -24,7 +24,8 @@ FARPZones.verbose = false
|
||||
2.0.2 - clean-up
|
||||
verbosity enhancements
|
||||
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
|
||||
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
|
||||
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
|
||||
2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded
|
||||
|
||||
--]]--
|
||||
|
||||
@ -331,9 +332,7 @@ end
|
||||
|
||||
function FARPZones.produceVehicles(theFarp)
|
||||
local theZone = theFarp.zone
|
||||
-- trigger.action.outText("entering veh prod run for farp zone <" .. theZone.name .. ">, owner is <" .. theFarp.owner .. ">", 30)
|
||||
--end
|
||||
|
||||
|
||||
-- abort production if farp is owned by neutral and
|
||||
-- neutralproduction is false
|
||||
if theFarp.owner == 0 and not theFarp.neutralProduction then
|
||||
@ -382,18 +381,7 @@ function FARPZones.produceVehicles(theFarp)
|
||||
|
||||
-- spawn resource vehicles
|
||||
FARPZones.produceResourceVehicles(theFarp, theCoalition)
|
||||
--[[--
|
||||
unitTypes = FARPZones.resourceTypes
|
||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||
theCoalition,
|
||||
theFarp.name .. "-R" .. theFarp.count, -- must be unique
|
||||
theFarp.resZone,
|
||||
unitTypes,
|
||||
"line_v",
|
||||
theFarp.resHeading)
|
||||
theFarp.resources = theGroup
|
||||
theFarp.resourceData = theData
|
||||
--]]--
|
||||
|
||||
-- update unique counter
|
||||
theFarp.count = theFarp.count + 1
|
||||
end
|
||||
@ -508,7 +496,6 @@ function FARPZones.saveData()
|
||||
-- iterate all farp data and put them into a container each
|
||||
for theZone, theFARP in pairs(FARPZones.allFARPZones) do
|
||||
fName = theZone.name
|
||||
--trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
|
||||
local fData = {}
|
||||
fData.owner = theFARP.owner
|
||||
fData.defenderData = dcsCommon.clone(theFARP.defenderData)
|
||||
@ -528,6 +515,17 @@ function FARPZones.saveData()
|
||||
return theData
|
||||
end
|
||||
|
||||
function FARPZones.delayedSSB()
|
||||
-- invoke SSBClient to re-scan all airfields
|
||||
-- if it is loaded in the mission
|
||||
if FARPZones.verbose then
|
||||
trigger.action.outText("FARPz: delayed SSB invocation", 30)
|
||||
end
|
||||
if cfxSSBClient then
|
||||
cfxSSBClient.setSlotAccessByAirfieldOwner()
|
||||
end
|
||||
end
|
||||
|
||||
function FARPZones.loadMission()
|
||||
local theData = persistence.getSavedDataForModule("FARPZones")
|
||||
if not theData then
|
||||
@ -548,27 +546,9 @@ function FARPZones.loadMission()
|
||||
theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
|
||||
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
|
||||
|
||||
--[[--
|
||||
local groupData = fData.defenderData
|
||||
if groupData and #groupData.units > 0 then
|
||||
local cty = groupData.cty
|
||||
local cat = groupData.cat
|
||||
theFARP.defenders = coalition.addGroup(cty, cat, groupData)
|
||||
end
|
||||
|
||||
groupData = fData.resourceData
|
||||
if groupData and #groupData.units > 0 then
|
||||
local cty = groupData.cty
|
||||
local cat = groupData.cat
|
||||
theFARP.resources = coalition.addGroup(cty, cat, groupData)
|
||||
end
|
||||
--]]--
|
||||
FARPZones.produceVehicles(theFARP) -- do full defender and resource cycle
|
||||
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map
|
||||
-- if (not theFARP.defenders) and (not theFARP.resources) then
|
||||
-- we instigate a resource and defender drop
|
||||
-- FARPZones.produceVehicles(theFARP)
|
||||
-- end
|
||||
|
||||
else
|
||||
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
|
||||
end
|
||||
@ -580,10 +560,8 @@ end
|
||||
-- Start
|
||||
--
|
||||
function FARPZones.releaseFARPS()
|
||||
-- trigger.action.outText("Releasing hold on FARPS", 30)
|
||||
for idx, aFarp in pairs(FARPZones.lockup) do
|
||||
aFarp:autoCapture(true)
|
||||
-- trigger.action.outText("releasing farp <" .. aFarp:getName() .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
@ -592,13 +570,10 @@ function FARPZones.readConfig()
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("farpZonesConfig")
|
||||
end
|
||||
|
||||
FARPZones.verbose = theZone.verbose
|
||||
|
||||
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
|
||||
|
||||
FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -635,8 +610,6 @@ function FARPZones.start()
|
||||
for k, aZone in pairs(theZones) do
|
||||
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
|
||||
FARPZones.addFARPZone(aFARP) -- add to managed zones
|
||||
-- moved FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
|
||||
-- moved FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
|
||||
if FARPZones.verbose then
|
||||
trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30)
|
||||
end
|
||||
@ -656,6 +629,7 @@ function FARPZones.start()
|
||||
FARPZones.startingUp = false -- not needed / read anywhere
|
||||
|
||||
timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5)
|
||||
timer.scheduleFunction(FARPZones.delayedSSB, {}, timer.getTime() + 10)
|
||||
|
||||
if FARPZones.refresh > 0 then
|
||||
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)
|
||||
|
||||
@ -75,13 +75,9 @@ function rndFlags.createRNDWithZone(theZone)
|
||||
-- trigger flag
|
||||
if theZone:hasProperty("f?") then
|
||||
theZone.triggerFlag = theZone:getStringFromZoneProperty("f?", "none")
|
||||
end
|
||||
|
||||
if theZone:hasProperty("in?") then
|
||||
elseif theZone:hasProperty("in?") then
|
||||
theZone.triggerFlag = theZone:getStringFromZoneProperty("in?", "none")
|
||||
end
|
||||
|
||||
if theZone:hasProperty("rndPoll?") then
|
||||
elseif theZone:hasProperty("rndPoll?") then
|
||||
theZone.triggerFlag = theZone:getStringFromZoneProperty("rndPoll?", "none")
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxSSBClient = {}
|
||||
cfxSSBClient.version = "4.0.1"
|
||||
cfxSSBClient.version = "5.0.0"
|
||||
cfxSSBClient.verbose = false
|
||||
cfxSSBClient.singleUse = false -- set to true to block crashed planes
|
||||
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
|
||||
@ -8,7 +8,7 @@ cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
|
||||
|
||||
cfxSSBClient.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxMX", --"cfxGroups", -- for slot access
|
||||
"cfxMX", -- for ME Player data access
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
|
||||
@ -17,7 +17,13 @@ Version History
|
||||
4.0.0 - dmlZones
|
||||
- cfxMX instead of cfxGroups
|
||||
4.0.1 - check slot availability immediately upon start
|
||||
- ssb autoenable option
|
||||
- ssb auto-enable option
|
||||
5.0.0 - re-write: support for DCS new dynamic spawns
|
||||
- work-around for DCS bug that passes dead / deallocated objects
|
||||
- SINGLE-USE and dynamic spawns are mutually exclusive
|
||||
as single-use cannot cover dynamic slots, so authors must
|
||||
disable or limit the amount of planes manually
|
||||
|
||||
--]]--
|
||||
|
||||
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
|
||||
@ -36,9 +42,9 @@ cfxSSBClient.slotActions = {
|
||||
cfxSSBClient.keepInAirGroups = false -- if false we only look at planes starting on the ground
|
||||
-- setting this to true only makes sense if you plan to bind in-air starts to airfields
|
||||
|
||||
cfxSSBClient.playerGroups = {}
|
||||
cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts
|
||||
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying
|
||||
cfxSSBClient.playerGroups = {} -- indexed by groupName. group data with .airfield attribute
|
||||
cfxSSBClient.closedAirfields = {} -- list that closes airfields for all aircrafts
|
||||
cfxSSBClient.playerPlanes = {} -- names of unit that a player is flying indexed by unit name
|
||||
cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane
|
||||
cfxSSBClient.slotState = {} -- keeps a record of which slot has which value. For persistence and debugging
|
||||
cfxSSBClient.occupiedUnits = {} -- by unit name if occupied to prevent kicking. clears after crash or leaving plane
|
||||
@ -154,6 +160,7 @@ end
|
||||
function cfxSSBClient.setSlotAccessForGroup(theGroup)
|
||||
if not theGroup then return end
|
||||
-- WARNING: theGroup is cfxGroup record
|
||||
-- amended for dynamic groups
|
||||
local theName = theGroup.name
|
||||
|
||||
-- we now check if any plane of that group is still
|
||||
@ -163,15 +170,27 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
|
||||
|
||||
-- we now iterate all playerUnits in theGroup.
|
||||
-- theGroup is cfxGroup
|
||||
for idx, playerData in pairs (theGroup.playerUnits) do
|
||||
local uName = playerData.name
|
||||
if cfxSSBClient.occupiedUnits[uName] then
|
||||
if theGroup.playerUnits then
|
||||
-- this is a ME unit
|
||||
for idx, playerData in pairs (theGroup.playerUnits) do
|
||||
local uName = playerData.name
|
||||
if cfxSSBClient.occupiedUnits[uName] then
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
else
|
||||
-- this is a dynamic unit
|
||||
if cfxSSBClient.occupiedUnits[theGroup.uName] then
|
||||
local uName = theGroup.uName
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
|
||||
trigger.action.outText("+++ssbc: DYNAMIC unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- when we get here, no unit in the entire group is occupied
|
||||
local theMatchingAirfield = theGroup.airfield
|
||||
@ -230,8 +249,6 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
|
||||
end
|
||||
trigger.action.setUserFlag(theName, blockState)
|
||||
cfxSSBClient.slotState[theName] = blockState
|
||||
--if cfxSSBClient.verbose then
|
||||
--end
|
||||
else
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30)
|
||||
@ -252,18 +269,22 @@ function cfxSSBClient.setSlotAccessForUnit(theUnit) -- calls setSlotAccessForGro
|
||||
end
|
||||
|
||||
function cfxSSBClient.getPlayerGroupForGroupNamed(aName)
|
||||
--is now indexed !!
|
||||
return cfxSSBClient.playerGroups[aName]
|
||||
--[[--
|
||||
local pGroups = cfxSSBClient.playerGroups
|
||||
for idx, theGroup in pairs(pGroups) do
|
||||
if theGroup.name == aName then return theGroup end
|
||||
end
|
||||
return nil
|
||||
return nil
|
||||
--]]--
|
||||
end
|
||||
|
||||
function cfxSSBClient.setSlotAccessByAirfieldOwner()
|
||||
-- get all groups that have a player-controlled aircraft
|
||||
-- now uses cached, reduced set of player planes
|
||||
local pGroups = cfxSSBClient.playerGroups
|
||||
for idx, theGroup in pairs(pGroups) do
|
||||
local pGroups = cfxSSBClient.playerGroups -- indexed by name
|
||||
for gName, theGroup in pairs(pGroups) do
|
||||
cfxSSBClient.setSlotAccessForGroup(theGroup)
|
||||
end
|
||||
end
|
||||
@ -294,14 +315,8 @@ function cfxSSBClient:onEvent(event)
|
||||
end
|
||||
return
|
||||
end
|
||||
local curH = theUnit:getLife()
|
||||
local maxH = theUnit:getLife0()
|
||||
if not theUnit.getName then return end -- WTF???
|
||||
local uName = theUnit:getName()
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: Player leaves unit <" .. uName .. ">", 30)
|
||||
trigger.action.outText("+++SSB: unit health check: " .. curH .. " of " .. maxH, 30)
|
||||
end
|
||||
|
||||
cfxSSBClient.occupiedUnits[uName] = nil -- forget I was occupied
|
||||
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
|
||||
return
|
||||
@ -317,9 +332,10 @@ function cfxSSBClient:onEvent(event)
|
||||
end
|
||||
|
||||
-- write down player names and planes
|
||||
if event.id == 15 then -- birth
|
||||
if event.id == 15 then -- birth / spawn
|
||||
if not event.initiator then return end
|
||||
local theUnit = event.initiator -- we know this exists
|
||||
if not theUnit.getName then return end -- hardening
|
||||
local uName = theUnit:getName()
|
||||
if not uName then return end
|
||||
-- player entered unit?
|
||||
@ -336,6 +352,12 @@ function cfxSSBClient:onEvent(event)
|
||||
end
|
||||
-- remember this unit as player controlled plane
|
||||
-- because player and plane can easily disconnect
|
||||
-- find out if this is a dynamic spawn
|
||||
local isDynamic = cfxMX.isDynamicPlayer(theUnit)
|
||||
if isDynamic then
|
||||
trigger.action.outText("+++SSBC: detected dynamic player spawn for unit <" .. uName .. ">, id <" .. theUnit:getID() .. ">, group <" .. theUnit:getGroup():getName() .. ">, player <" .. playerName .. ">", 30)
|
||||
cfxSSBClient.amendPlayerData(theUnit) -- get airport and add to managed slots
|
||||
end
|
||||
cfxSSBClient.playerPlanes[uName] = playerName
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSBC:SU: noted " .. playerName .. " piloting player unit " .. uName, 30)
|
||||
@ -349,16 +371,21 @@ function cfxSSBClient:onEvent(event)
|
||||
if event.id == 5 then -- crash PRE-processing
|
||||
if not event.initiator then return end
|
||||
local theUnit = event.initiator
|
||||
if not theUnit.getName then return end
|
||||
local uName = theUnit:getName()
|
||||
cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied
|
||||
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
|
||||
-- DO NOT RETURN NOW!!! singleuse proccing follows
|
||||
end
|
||||
|
||||
if cfxSSBClient.singleUse and event.id == 5 then -- crash
|
||||
--if not event.initiator then return end
|
||||
local theUnit = event.initiator
|
||||
if not theUnit then return end
|
||||
if not theUnit.getName then return end
|
||||
local uName = theUnit:getName()
|
||||
if not uName then return end
|
||||
if not theUnit.getGroup then return end
|
||||
local theGroup = theUnit:getGroup()
|
||||
if not theGroup then return end
|
||||
-- see if a player plane
|
||||
@ -371,6 +398,7 @@ function cfxSSBClient:onEvent(event)
|
||||
return
|
||||
end
|
||||
-- if we get here, a player-owned plane has crashed
|
||||
if not theGroup.getName then return end -- better safe than sorry
|
||||
local gName = theGroup:getName()
|
||||
if not gName then return end
|
||||
|
||||
@ -435,27 +463,34 @@ end
|
||||
|
||||
-- pre-process static player data to minimize
|
||||
-- processor load on checks
|
||||
function cfxSSBClient.processPlayerData()
|
||||
cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
|
||||
local pGroups = cfxSSBClient.playerGroups
|
||||
function cfxSSBClient.processSSBPlayerData()
|
||||
--cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
|
||||
local pGroups = cfxSSBClient.SSBPlayerData -- cfxSSBClient.playerGroups
|
||||
local filteredPlayers = {}
|
||||
for idx, theGroup in pairs(pGroups) do
|
||||
for gName, theGroup in pairs(pGroups) do
|
||||
if theGroup.airfield ~= nil or cfxSSBClient.keepInAirGroups or
|
||||
cfxSSBClient.singleUse then
|
||||
-- only transfer groups that have airfields (or also keepInAirGroups or when single-use)
|
||||
-- attached. Ignore the rest as they are
|
||||
-- always fine
|
||||
table.insert(filteredPlayers, theGroup)
|
||||
--table.insert(filteredPlayers, theGroup)
|
||||
filteredPlayers[gName] = theGroup
|
||||
end
|
||||
end
|
||||
cfxSSBClient.playerGroups = filteredPlayers
|
||||
-- we can now relinquish SSBPlayerData
|
||||
cfxSSBClient.SSBPlayerData = nil
|
||||
end
|
||||
|
||||
-- add airfield information to each player group
|
||||
-- WARNING: AMENDS/MODIFIES DATA IN MX TO CONTAIN AIRFIELDS
|
||||
-- now changed to internal clones
|
||||
function cfxSSBClient.processGroupData()
|
||||
local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute
|
||||
local processed = {}
|
||||
for idx, theGroup in pairs(pGroups) do
|
||||
-- we always use the first player's plane as referenced
|
||||
local cGroup = dcsCommon.clone(theGroup)
|
||||
local playerData = theGroup.playerUnits[1]
|
||||
local theAirfield = nil
|
||||
local delta = -1
|
||||
@ -471,20 +506,52 @@ function cfxSSBClient.processGroupData()
|
||||
end
|
||||
if delta > cfxSSBClient.maxAirfieldRange then
|
||||
-- forget airfield
|
||||
theAirfield = nil
|
||||
theAirfield = nil
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " unlinked - too far from airfield" , 30)
|
||||
end
|
||||
-- end
|
||||
else
|
||||
cGroup.airfield = theAirfield -- we update the clone
|
||||
-- add to my player groups, indexed by group name
|
||||
processed[theGroup.name] = cGroup -- we keep the clone
|
||||
end
|
||||
theGroup.airfield = theAirfield
|
||||
else
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " start option " .. action .. " does not concern SSB", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
cfxSSBClient.SSBPlayerData = processed -- remember all relevant clones for post-processing
|
||||
end
|
||||
|
||||
function cfxSSBClient.amendPlayerData(theUnit)
|
||||
-- enter with single, dynamically spawning unit and add an
|
||||
-- entry for cfxSSBClient.SSBPlayerData to allow entry for
|
||||
-- group's airbase / FARP
|
||||
local dynGroup = theUnit:getGroup()
|
||||
local theGroup = {} -- entry into db
|
||||
theGroup.name = dynGroup:getName()
|
||||
local thePoint = theUnit:getPoint()
|
||||
local theAirfield, delta = cfxSSBClient.getClosestAirbaseTo(thePoint)
|
||||
local afName = theAirfield:getName()
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " closest to AF " .. afName .. ": " .. math.floor(delta) .. "m" , 30)
|
||||
end
|
||||
if delta > cfxSSBClient.maxAirfieldRange then
|
||||
-- forget airfield
|
||||
--theAirfield = nil
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " unlinked - too far from airfield (???)" , 30 )
|
||||
end
|
||||
else
|
||||
if cfxSSBClient.verbose then
|
||||
trigger.action.outText("+++SSB: DYNAMIC group: " .. theGroup.name .. " added to SSBPlayerData for slot management" , 30)
|
||||
end
|
||||
theGroup.uName = theUnit:getName()
|
||||
cfxSSBClient.playerGroups[theGroup.name] = theGroup
|
||||
end
|
||||
end
|
||||
--
|
||||
-- read config zone
|
||||
--
|
||||
@ -589,7 +656,7 @@ function cfxSSBClient.start()
|
||||
|
||||
-- process player data to minimize effort and build cache
|
||||
-- into cfxSSBClient.playerGroups
|
||||
cfxSSBClient.processPlayerData()
|
||||
cfxSSBClient.processSSBPlayerData() -- processPlayerData()
|
||||
|
||||
-- process ssbc zones
|
||||
-- for in-mission DML interface
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
tdz = {}
|
||||
tdz.version = "1.0.3"
|
||||
tdz.version = "1.1.0"
|
||||
tdz.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -18,6 +18,7 @@ VERSION HISTORY
|
||||
1.0.2 - manual placement option
|
||||
filters FARPs
|
||||
1.0.3 - "manual" now defaults to false
|
||||
1.1.0 - now supports event 55 (runway touch)
|
||||
|
||||
--]]--
|
||||
|
||||
@ -326,7 +327,7 @@ function tdz:onEvent(event)
|
||||
if not theUnit.getPlayerName then return end
|
||||
local playerName = theUnit:getPlayerName()
|
||||
if not playerName then return end
|
||||
if event.id == 4 then
|
||||
if event.id == 4 or event.id == 55 then
|
||||
-- player landed
|
||||
tdz.playerLanded(theUnit, playerName)
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
airfield = {}
|
||||
airfield.version = "2.1.1"
|
||||
airfield.version = "2.2.0"
|
||||
airfield.requiredLibs = {
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
@ -21,7 +21,7 @@ airfield.allAirfields = {} -- inexed by af name, db entries: base, cat
|
||||
-- support for FARPS as well
|
||||
2.1.0 - added support for makeNeutral?
|
||||
2.1.1 - bug fixing for DCS 2.9x airfield retrofit
|
||||
|
||||
2.2.0 - dmlZone:getCoalition() / masterowner adaptation for owner
|
||||
--]]--
|
||||
|
||||
-- init all airfields DB
|
||||
@ -109,11 +109,6 @@ function airfield.createAirFieldFromZone(theZone)
|
||||
airfield.assumeControl(theZone)
|
||||
end
|
||||
|
||||
if theZone:hasProperty("ownedBy#") then
|
||||
theZone.ownedBy = theZone:getStringFromZoneProperty("ownedBy#", "<none>")
|
||||
trigger.action.setUserFlag(theZone.ownedBy, theZone.owner)
|
||||
end
|
||||
|
||||
-- if fixed attribute, we switch to that color and keep it fixed.
|
||||
-- can be overridden by either makeXX or autoCap.
|
||||
if theZone:hasProperty("fixed") then
|
||||
@ -124,6 +119,11 @@ function airfield.createAirFieldFromZone(theZone)
|
||||
theZone.owner = theFixed
|
||||
end
|
||||
|
||||
if theZone:hasProperty("ownedBy#") then
|
||||
theZone.ownedBy = theZone:getStringFromZoneProperty("ownedBy#", "<none>")
|
||||
trigger.action.setUserFlag(theZone.ownedBy, theZone.owner)
|
||||
end
|
||||
|
||||
-- index by name, and warn if duplicate associate
|
||||
if airfield.myAirfields[theZone.afName] then
|
||||
trigger.action.outText("+++airF: WARNING - zone <" .. theZone.name .. "> redefines airfield <" .. theZone.afName .. ">, discarded!", 30)
|
||||
@ -182,7 +182,7 @@ function airfield.showAirfield(theZone)
|
||||
|
||||
local lineColor = theZone.redLine -- {1.0, 0, 0, 1.0} -- red
|
||||
local fillColor = theZone.redFill -- {1.0, 0, 0, 0.2} -- red
|
||||
local owner = theZone.owner
|
||||
local owner = theZone:getCoalition() -- .owner
|
||||
if owner == 2 then
|
||||
lineColor = theZone.blueLine -- {0.0, 0, 1.0, 1.0}
|
||||
fillColor = theZone.blueFill -- {0.0, 0, 1.0, 0.2}
|
||||
@ -315,6 +315,7 @@ function airfield.update()
|
||||
if theZone.owner ~= 1 then -- only send cap event when capped
|
||||
airfield.airfieldCaptured(theAirfield)
|
||||
end
|
||||
theZone.owner = 1
|
||||
end
|
||||
|
||||
if theZone.makeBlue and theZone:testZoneFlag(theZone.makeBlue, theZone.triggerMethod, "lastMakeBlue") then
|
||||
@ -329,6 +330,7 @@ function airfield.update()
|
||||
if theZone.owner ~= 2 then -- only send cap event when capped
|
||||
airfield.airfieldCaptured(theAirfield)
|
||||
end
|
||||
theZone.owner = 2
|
||||
end
|
||||
|
||||
if theZone.makeNeutral and theZone:testZoneFlag(theZone.makeNeutral, theZone.triggerMethod, "lastMakeNeutral") then
|
||||
@ -343,6 +345,7 @@ function airfield.update()
|
||||
if theZone.owner ~= 0 then -- only send cap event when capped
|
||||
airfield.airfieldCaptured(theAirfield) -- 0 cap will not cause any signals, but we do this anyway
|
||||
end
|
||||
theZone.owner = 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
bombRange = {}
|
||||
bombRange.version = "2.0.0"
|
||||
bombRange.version = "2.0.2"
|
||||
bombRange.dh = 1 -- meters above ground level burst
|
||||
|
||||
bombRange.requiredLibs = {
|
||||
@ -13,7 +13,7 @@ VERSION HISTORY
|
||||
*after* impact on high-resolution scans (30fps)
|
||||
set resolution to 30 ups by default
|
||||
order of events: check kills against dropping projectiles
|
||||
collecd dead, and compare against missing erdnance while they are fresh
|
||||
collect dead, and compare against missing erdnance while they are fresh
|
||||
GC
|
||||
interpolate hits on dead when looking at kills and projectile does
|
||||
not exist
|
||||
@ -26,6 +26,9 @@ VERSION HISTORY
|
||||
2.0.0 - support for radioMainMenu
|
||||
- support for types
|
||||
- types can have wild cards
|
||||
2.0.1 - says hi! on start
|
||||
- fixes for DCS Jul 11 bugs
|
||||
2.0.2 - fixes for DCS Jul 22 bugs
|
||||
|
||||
|
||||
--]]--
|
||||
@ -197,7 +200,7 @@ function bombRange.initCommsForUnit(theUnit)
|
||||
if bombRange.mainMenu then
|
||||
mainMenu = radioMenu.getMainMenuFor(bombRange.mainMenu) -- nilling both next params will return menus[0]
|
||||
end
|
||||
|
||||
if not theUnit.getName then return end -- Jul-22 DCS bug
|
||||
local uName = theUnit:getName()
|
||||
local pName = theUnit:getPlayerName()
|
||||
local theGroup = theUnit:getGroup()
|
||||
@ -274,6 +277,7 @@ end
|
||||
-- Event Proccing
|
||||
--
|
||||
function bombRange.suspectedHit(weapon, target)
|
||||
if not Object.isExist(weapon) then return end -- DCS july 11 2024 issue
|
||||
local wType = weapon:getTypeName()
|
||||
if not target then return end
|
||||
if target:getCategory() == 5 then -- scenery
|
||||
@ -407,15 +411,19 @@ end
|
||||
function bombRange:onEvent(event)
|
||||
if not event.initiator then return end
|
||||
local theUnit = event.initiator
|
||||
if not Unit.isExist(theUnit) then return end -- DCS issue Jul-11
|
||||
if not theUnit.getName then return end -- DCS issue Jul-22
|
||||
|
||||
if event.id == 2 then -- hit: weapon still exists
|
||||
if not event.weapon then return end
|
||||
if not Object.isExist(event.weapon) then return end -- Jul-11 issue
|
||||
bombRange.suspectedHit(event.weapon, event.target)
|
||||
return
|
||||
end
|
||||
|
||||
if event.id == 28 then -- kill: similar to hit, but due to new mechanics not reliable
|
||||
if not event.weapon then return end
|
||||
if not Object.isExist(event.weapon) then return end -- Jul-11 issue
|
||||
bombRange.suspectedHit(event.weapon, event.target)
|
||||
return
|
||||
end
|
||||
@ -425,6 +433,7 @@ function bombRange:onEvent(event)
|
||||
-- these events can come *before* weapon disappears
|
||||
local killDat = {}
|
||||
killDat.victim = event.initiator
|
||||
if not Object.isExist(event.initiator) then return end -- Jul-11 issue
|
||||
killDat.p = event.initiator:getPoint()
|
||||
killDat.when = timer.getTime()
|
||||
killDat.name = dcsCommon.uuid("vic")
|
||||
@ -454,7 +463,8 @@ function bombRange:onEvent(event)
|
||||
end
|
||||
local w = event.weapon
|
||||
local b = {}
|
||||
local bName = w:getName()
|
||||
local bName = "unknown"
|
||||
if w.getName then bName = w:getName() end -- Jul-22 DCS bug
|
||||
b.name = bName
|
||||
b.type = w:getTypeName()
|
||||
-- may need to verify type: how do we handle clusters or flares?
|
||||
@ -793,6 +803,8 @@ function bombRange.start()
|
||||
-- start GC
|
||||
bombRange.GC()
|
||||
|
||||
-- say hi!
|
||||
trigger.action.outText("cf/x Bomb Range v" .. bombRange.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
camp = {}
|
||||
camp.ups = 1
|
||||
camp.version = "1.0.2"
|
||||
camp.version = "1.1.0"
|
||||
camp.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -18,6 +18,7 @@ VERSION HISTORY
|
||||
- actionSound
|
||||
- output sound with communications
|
||||
1.0.2 - integration with FARPZones
|
||||
1.1.0 - support for DCS 2.9.6 dynamic spawns
|
||||
--]]--
|
||||
--
|
||||
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
|
||||
@ -110,24 +111,55 @@ function camp.update()
|
||||
end
|
||||
|
||||
function camp:onEvent(theEvent)
|
||||
|
||||
if not theEvent then return end
|
||||
if not theEvent.initiator then return end
|
||||
local theUnit = theEvent.initiator
|
||||
-- if not theUnit.getName then return end
|
||||
-- if not theUnit.getPlayerName then return end
|
||||
if not cfxMX.isDynamicPlayer(theUnit) then return end
|
||||
local id = theEvent.id
|
||||
if id == 15 then -- birth
|
||||
camp.lateProcessPlayer(theUnit)
|
||||
if camp.verbose then
|
||||
trigger.action.outText("camp: late player processing for <" .. theUnit:getName() .. ">", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Comms
|
||||
--
|
||||
function camp.lateProcessPlayer(theUnit)
|
||||
if not theUnit then return end
|
||||
if not theUnit.getGroup then return end
|
||||
local theGroup = theUnit:getGroup()
|
||||
local gName = theGroup:getName()
|
||||
local gID = theGroup:getID()
|
||||
camp.installComsFor(gID, gName)
|
||||
end
|
||||
|
||||
function camp.installComsFor(gID, gName)
|
||||
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
|
||||
camp.roots[gName] = theRoot
|
||||
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
|
||||
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
|
||||
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
|
||||
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"})
|
||||
end
|
||||
|
||||
function camp.processPlayers()
|
||||
-- install coms stump for all players. they will be switched in/out
|
||||
-- whenever it is apropriate
|
||||
for idx, gData in pairs(cfxMX.playerGroupByName) do
|
||||
gID = gData.groupId
|
||||
gName = gData.name
|
||||
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
|
||||
--[[-- local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
|
||||
camp.roots[gName] = theRoot
|
||||
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
|
||||
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
|
||||
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
|
||||
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"})
|
||||
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"}) --]]--
|
||||
camp.installComsFor(gID, gName)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxMX = {}
|
||||
cfxMX.version = "2.0.2"
|
||||
cfxMX.version = "2.1.0"
|
||||
cfxMX.verbose = false
|
||||
--[[--
|
||||
Mission data decoder. Access to ME-built mission structures
|
||||
@ -13,7 +13,12 @@ cfxMX.verbose = false
|
||||
- harmonized with cfxGroups
|
||||
2.0.1 - groupHotByName
|
||||
2.0.2 - partOfGroupDataInZone(), allGroupsInZoneByData() from milHelo
|
||||
|
||||
2.0.3 - allGroupsInZoneByData supports type filtering
|
||||
2.1.0 - support for dynamically spawning player unit detection
|
||||
- new isDynamicPlayer()
|
||||
- new isMEPlayer()
|
||||
- new isMEPlayerGroup()
|
||||
|
||||
--]]--
|
||||
cfxMX.groupNamesByID = {}
|
||||
cfxMX.groupIDbyName = {}
|
||||
@ -37,16 +42,14 @@ cfxMX.playerUnit2Group = {} -- returns a group data for player units.
|
||||
|
||||
cfxMX.groups = {} -- all groups indexed b yname, cfxGroups folded into cfxMX
|
||||
--[[-- group objects are
|
||||
{
|
||||
name= "",
|
||||
coalition = "" (red, blue, neutral),
|
||||
coanum = # (0, 1, 2 for neutral, red, blue)
|
||||
category = "" (helicopter, ship, plane, vehicle, static),
|
||||
hasPlayer = true/false,
|
||||
playerUnits = {} (for each player unit in group: name, point, action)
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
name= "",
|
||||
coalition = "" (red, blue, neutral),
|
||||
coanum = # (0, 1, 2 for neutral, red, blue)
|
||||
category = "" (helicopter, ship, plane, vehicle, static),
|
||||
hasPlayer = true/false,
|
||||
playerUnits = {} (for each player unit in group: name, point, action)
|
||||
}
|
||||
--]]--
|
||||
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
|
||||
if not fetchOriginal then fetchOriginal = false end
|
||||
@ -359,11 +362,14 @@ function cfxMX.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
|
||||
return false
|
||||
end
|
||||
|
||||
function cfxMX.allGroupsInZoneByData(theZone) -- returns groups indexed by name and count
|
||||
function cfxMX.allGroupsInZoneByData(theZone, cat) -- returns groups indexed by name and count
|
||||
if not cat then cat = {"helicopter", "ship", "plane", "vehicle" } end
|
||||
if type(cat) == "string" then cat = {cat} end
|
||||
local theGroupsInZone = {}
|
||||
local count = 0
|
||||
for groupName, groupData in pairs(cfxMX.groupDataByName) do
|
||||
if groupData.units then
|
||||
local gType = cfxMX.groupTypeByName[groupName]
|
||||
if dcsCommon.arrayContainsString(cat, gType) and groupData.units then
|
||||
if cfxMX.partOfGroupDataInZone(theZone, groupData.units) then
|
||||
theGroupsInZone[groupName] = groupData -- DATA! work on clones!
|
||||
count = count + 1
|
||||
@ -375,7 +381,36 @@ function cfxMX.allGroupsInZoneByData(theZone) -- returns groups indexed by name
|
||||
end
|
||||
return theGroupsInZone, count
|
||||
end
|
||||
|
||||
|
||||
function cfxMX.isDynamicPlayer(theUnit)
|
||||
if not theUnit then return false end
|
||||
if not theUnit.getName then return false end
|
||||
if not theUnit.getPlayerName then return false end
|
||||
if not theUnit:getPlayerName() then return false end
|
||||
local uName = theUnit:getName()
|
||||
if cfxMX.playerUnitByName[uName] then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
function cfxMX.isMEPlayer(theUnit)
|
||||
if not theUnit then return false end
|
||||
if not theUnit.getName then return false end
|
||||
if not theUnit.getPlayerName then return false end
|
||||
if not theUnit:getPlayerName() then return false end
|
||||
local uName = theUnit:getName()
|
||||
if cfxMX.playerUnitByName[uName] then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function cfxMX.isMEPlayerGroup(theUnit)
|
||||
if not theUnit then return false end
|
||||
if not theUnit.getName then return end
|
||||
if not theUnit.getPlayerName then return end
|
||||
local uName = theUnit:getName()
|
||||
if cfxMX.playerUnitByName[uName] then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function cfxMX.start()
|
||||
cfxMX.createCrossReferences()
|
||||
if cfxMX.verbose then
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxZones = {}
|
||||
cfxZones.version = "4.3.6"
|
||||
cfxZones.version = "4.4.2"
|
||||
|
||||
-- cf/x zone management module
|
||||
-- reads dcs zones and makes them accessible and mutable
|
||||
@ -9,38 +9,6 @@ cfxZones.version = "4.3.6"
|
||||
--
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
- 4.0.0 - dmlZone OOP API started
|
||||
- code revision / refactoring
|
||||
- moved createPoint and copxPoint to dcsCommon, added bridging code
|
||||
- re-routed all createPoint() invocations to dcsCommon
|
||||
- removed anyPlayerInZone() because of cfxPlayer dependency
|
||||
- numberArrayFromString() moved to dcsCommon, bridged
|
||||
- flagArrayFromString() moved to dcsCommon, bridged
|
||||
- doPollFlag() can differentiate between number method and string method
|
||||
to enable passing an immediate negative value
|
||||
- getNumberFromZoneProperty() enforces number return even on default
|
||||
- immediate method switched to preceeding '#', to resolve conflict witzh
|
||||
negative numbers, backwards compatibility with old (dysfunctional) method
|
||||
- 4.0.1 - dmlZone:getName()
|
||||
- 4.0.2 - removed verbosity from declutterZone (both versions)
|
||||
- 4.0.3 - new processDynamicVZU()
|
||||
- wildcard uses processDynamicVZU
|
||||
- 4.0.4 - setFlagValue now supports multiple flags (OOP and classic)
|
||||
- doSetFlagValue optimizations
|
||||
- 4.0.5 - dynamicAB wildcard
|
||||
- processDynamicValueVU
|
||||
- 4.0.6 - hash mark forgotten QoL
|
||||
- 4.0.7 - drawZone()
|
||||
- 4.0.8 - markZoneWithObjects()
|
||||
- cleanup
|
||||
- markCenterWithObject
|
||||
- markPointWithObject
|
||||
- 4.0.9 - createPolyZone now correctly returns new zone
|
||||
- createSimplePolyZone correctly passes location to createPolyZone
|
||||
- createPolyZone now correctly sets zone.point
|
||||
- createPolyZone now correctly inits dcsOrigin
|
||||
- createCircleZone noew correctly inits dcsOrigin
|
||||
- 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false)
|
||||
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
|
||||
- 4.1.1 - evalRemainder() updates
|
||||
- 4.1.2 - hash property missing warning
|
||||
@ -49,7 +17,7 @@ cfxZones.version = "4.3.6"
|
||||
- small optimization for randomInRange()
|
||||
- randomDelayFromPositiveRange also allows 0
|
||||
- 4.3.1 - new drawText() for zones
|
||||
- dmlZones:getClosestZone() bridge
|
||||
- dmlZone:getClosestZone() bridge
|
||||
- 4.3.2 - new getListFromZoneProperty()
|
||||
- 4.3.3 - hardened calculateZoneBounds
|
||||
- 4.3.4 - rewrote zone bounds for poly zones
|
||||
@ -57,7 +25,13 @@ cfxZones.version = "4.3.6"
|
||||
- 4.3.6 - tiny optimization in isPointInsideQuad
|
||||
- moving zone - hardening code for static objects
|
||||
- moving zones - now deriving dx, dy,uHeading from dcsCommon xref for linked zones
|
||||
|
||||
- 4.3.7 - corrected bug in processDynamicValues for lookup table
|
||||
- 4.4.0 - dmlZone:getCoalition()
|
||||
- dmlZone:getTypeName()
|
||||
- dmlZone supports masterOwner by default
|
||||
- dmlZone:getCoalition() dereferences masterOwner once
|
||||
-4.4.1 - better verbosity for error in doPollFlag()
|
||||
-4.4.2 - twn support for wildcards <twn: > and <loc:>
|
||||
--]]--
|
||||
|
||||
--
|
||||
@ -81,6 +55,13 @@ function dmlZone:new(o)
|
||||
return o
|
||||
end
|
||||
|
||||
-- dmlZone compatibility with DCS MSE objects:
|
||||
-- dmlZone:getName() -- returns zone.name attribute (from ME)
|
||||
-- dmlZone:getPoint() -- returns current point or dmlPoint
|
||||
-- dmlZone:getTypeName() -- returns "dmlZone"
|
||||
-- dmlZone:getCoalition -- returns owner
|
||||
|
||||
|
||||
--
|
||||
-- CLASSIC INTERFACE
|
||||
--
|
||||
@ -1615,7 +1596,7 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
|
||||
|
||||
else
|
||||
if method ~= "on" and method ~= "f=1" then
|
||||
trigger.action.outText("+++zones: unknown method <" .. method .. "> - using 'on'", 30)
|
||||
trigger.action.outText("+++zones: unknown method <" .. method .. "> for flag <" .. theFlag .. "> in zone <" .. theZone.name .. "> - setting to 1", 30)
|
||||
end
|
||||
-- default: on.
|
||||
-- trigger.action.setUserFlag(theFlag, 1)
|
||||
@ -2922,7 +2903,7 @@ function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
|
||||
-- access flag
|
||||
local val = cfxZones.getFlagValue(param, theZone)
|
||||
if not val or (val < 1) then val = 1 end
|
||||
if val > msgResponses then val = msgResponses end
|
||||
if val > #msgResponses then val = #msgResponses end
|
||||
|
||||
val = msgResponses[val]
|
||||
val = dcsCommon.trim(val)
|
||||
@ -2968,7 +2949,7 @@ end
|
||||
|
||||
-- process <lat/lon/ele/mgrs/lle/latlon/alt/vel/hdg/rhdg/type/player: zone/unit>
|
||||
function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
|
||||
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type", "player"}
|
||||
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type", "player", "twn", "loc"}
|
||||
local outMsg = inMsg
|
||||
local uHead = 0
|
||||
for idx, aLocale in pairs(locales) do
|
||||
@ -3053,6 +3034,23 @@ function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
|
||||
elseif aLocale == "rhdg" and (responses) then
|
||||
local offset = cfxZones.rspMapper360(uHead, #responses)
|
||||
locString = dcsCommon.trim(responses[offset])
|
||||
elseif aLocale == "twn" then
|
||||
if twn and towns then locString = twn.closestTownTo(thePoint)
|
||||
else locString = "!twn!" end
|
||||
elseif aLocale == "loc" then
|
||||
if twn and towns then
|
||||
local name, data, dist = twn.closestTownTo(thePoint)
|
||||
local units = "km"
|
||||
local mdist= dist * 0.539957
|
||||
dist = math.floor(dist/100) / 10
|
||||
mdist = math.floor(mdist/100) / 10
|
||||
if imperialUnits then
|
||||
dist = mdist
|
||||
units = "nm"
|
||||
end
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(thePoint, data.p)
|
||||
locString = dist .. units .. " " .. bear .. " of " .. name
|
||||
else locString = "!twn!" end
|
||||
else
|
||||
-- we have mgrs
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
|
||||
@ -3345,6 +3343,24 @@ function dmlZone:getName() -- no cfxZones.bridge!
|
||||
return self.name
|
||||
end
|
||||
|
||||
function dmlZone:getCoalition()
|
||||
-- automatically support masterOwner. Warning: cloners etc can reference itself!
|
||||
if self.masterOwner then return self.masterOwner.owner end -- zone must exist
|
||||
return self.owner
|
||||
end
|
||||
|
||||
function cfxZones.getCoalition(theZone)
|
||||
return theZone:getCoalition()
|
||||
end
|
||||
|
||||
function dmlZone:getTypeName()
|
||||
return "dmlZone"
|
||||
end
|
||||
|
||||
function cfxZones.getTypeName(theZone)
|
||||
return theZone:getTypeName()
|
||||
end
|
||||
|
||||
function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really Z, don't get confused!!!!
|
||||
theZone.linkedUnit = theUnit
|
||||
if not dx then dx = 0 end
|
||||
@ -3693,6 +3709,20 @@ function cfxZones.init()
|
||||
-- much like verbose, all zones have owner
|
||||
for n, aZone in pairs(cfxZones.zones) do
|
||||
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)
|
||||
|
||||
if aZone:hasProperty("masterOwner") then
|
||||
local mo = aZone:getStringFromZoneProperty("masterOwner", "forgotten master")
|
||||
mo = dcsCommon.trim(mo)
|
||||
if mo == "*" then mo = aZone.name end
|
||||
local mz = cfxZones.getZoneByName(mo)
|
||||
if not mz then
|
||||
trigger.action.outText("+++fcxZones: WARNING: Master Owner <" .. mo .. "> for zone <" .. aZone.name .. "> does not exist!", 30)
|
||||
else
|
||||
aZone.masterOwner = mz
|
||||
aZone.owner = mz.owner
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- enable all zone's verbose flags if present
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cloneZones = {}
|
||||
cloneZones.version = "2.3.0"
|
||||
cloneZones.version = "2.4.0"
|
||||
cloneZones.verbose = false
|
||||
cloneZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -56,6 +56,7 @@ cloneZones.respawnOnGroupID = true
|
||||
(undocumented, just to provide lazy people with a migration
|
||||
path) with wiper module
|
||||
- using "wipe?" will now create a warning
|
||||
2.4.0 - reworked masterOwner to fit with dmlZone
|
||||
--]]--
|
||||
|
||||
--
|
||||
@ -268,6 +269,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
theZone.cloneMethod = theZone:getStringFromZoneProperty("method", "inc") -- note string on number default
|
||||
end
|
||||
|
||||
--[[--
|
||||
if theZone:hasProperty("masterOwner") then
|
||||
theZone.masterOwner = theZone:getStringFromZoneProperty( "masterOwner", "*")
|
||||
theZone.masterOwner = dcsCommon.trim(theZone.masterOwner)
|
||||
@ -285,7 +287,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
if not theMaster then
|
||||
trigger.action.outText("clnZ: WARNING: cloner's <" .. theZone.name .. "> master owner named <" .. theZone.masterOwner .. "> does not exist!", 30)
|
||||
end
|
||||
theZone.masterOwner = theMaster
|
||||
end
|
||||
--]]--
|
||||
|
||||
theZone.turn = theZone:getNumberFromZoneProperty("turn", 0)
|
||||
|
||||
@ -692,7 +696,8 @@ function cloneZones.sameIDUnitData(theData)
|
||||
end
|
||||
|
||||
function cloneZones.resolveOwnership(spawnZone, ctry)
|
||||
if not spawnZone.masterOwner then return ctry end
|
||||
if not spawnZone.masterOwner then return ctry end -- old code
|
||||
--[[--
|
||||
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
|
||||
if not masterZone then
|
||||
trigger.action.outText("+++clnZ: cloner " .. spawnZone.name .. " could not find master owner <" .. spawnZone.masterOwner .. ">", 30)
|
||||
@ -702,8 +707,10 @@ function cloneZones.resolveOwnership(spawnZone, ctry)
|
||||
if not masterZone.owner then
|
||||
return ctry
|
||||
end
|
||||
--]]--
|
||||
|
||||
ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
|
||||
-- ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
|
||||
ctry = dcsCommon.getACountryForCoalition(spawnZone:getCoalition())
|
||||
return ctry
|
||||
end
|
||||
|
||||
@ -1694,6 +1701,8 @@ function cloneZones.hasLiveUnits(theZone)
|
||||
end
|
||||
|
||||
function cloneZones.resolveOwningCoalition(theZone)
|
||||
return theZone:getCoalition()
|
||||
--[[--
|
||||
if not theZone.masterOwner then return theZone.owner end
|
||||
local masterZone = cfxZones.getZoneByName(theZone.masterOwner)
|
||||
if not masterZone then
|
||||
@ -1701,6 +1710,7 @@ function cloneZones.resolveOwningCoalition(theZone)
|
||||
return theZone.owner
|
||||
end
|
||||
return masterZone.owner
|
||||
--]]--
|
||||
end
|
||||
|
||||
function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide)
|
||||
|
||||
1017
modules/convoy.lua
1017
modules/convoy.lua
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
csarManager = {}
|
||||
csarManager.version = "4.0.0"
|
||||
csarManager.version = "4.2.1"
|
||||
csarManager.ups = 1
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
@ -46,7 +46,11 @@ csarManager.ups = 1
|
||||
3.4.0 - global timeLimit option in config zone
|
||||
- fixes expiration bug when persisting data
|
||||
4.0.0 - support for mainMenu
|
||||
|
||||
4.0.1 - increased verbosity
|
||||
- fix for Jul-11 2024 DCS bugs
|
||||
4.1.0 - support for DCS 2.9.6 dynamic spawns
|
||||
4.2.0 - automatically support twn if present
|
||||
4.2.1 - added Chinook to csar default set (via common)
|
||||
|
||||
INTEGRATES AUTOMATICALLY WITH playerScore
|
||||
INTEGRATES WITH LIMITED AIRFRAMES
|
||||
@ -64,6 +68,8 @@ csarManager.requiredLibs = {
|
||||
|
||||
-- unitConfigs contain the config data for any helicopter
|
||||
-- currently in the game. The Array is indexed by unit name
|
||||
-- requires single-unit player groups
|
||||
-- compatible with DCS 2.9.6 dcs dynamic spawns
|
||||
csarManager.unitConfigs = {}
|
||||
|
||||
--
|
||||
@ -181,7 +187,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
|
||||
end
|
||||
|
||||
if csarManager.useRanks then
|
||||
-- local ranks = csarManager.ranks -- {"Lt", "Lt", "Lt", "Col", "Cpt", "WO", "WO"}
|
||||
local myRank = dcsCommon.pickRandom(csarManager.ranks)
|
||||
name = myRank .. " " .. name
|
||||
end
|
||||
@ -243,7 +248,6 @@ function csarManager.removeMission(theMission, pickup)
|
||||
if aMission ~= theMission then
|
||||
table.insert(newMissions, aMission)
|
||||
else
|
||||
-- csarManager.invokeRemovedMissionCallbacks(theMission)
|
||||
if pickup then
|
||||
csarManager.invokePickUpCallbacks(theMission)
|
||||
end
|
||||
@ -296,6 +300,7 @@ end
|
||||
|
||||
|
||||
function csarManager.getUnitConfig(theUnit) -- will create new config if not existing
|
||||
-- compatible with dynamic spawns for DCS 2.9.6
|
||||
if not theUnit then
|
||||
trigger.action.outText("+++csar: nil unit in get config!", 30)
|
||||
return nil
|
||||
@ -331,15 +336,22 @@ function csarManager:onEvent(event)
|
||||
|
||||
if not dcsCommon.isPlayerUnit(theUnit) then return end -- not a player unit
|
||||
|
||||
if csarManager.verbose then
|
||||
trigger.action.outText("csarM: player event <" .. event.id .. "> -- (=<" .. dcsCommon.event2text(event.id) .. ">)", 30)
|
||||
end
|
||||
|
||||
-- only proceed if troop carrier (no more helo checks, all troop carriers, so osprey and harrier can be used if so desired)
|
||||
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||
|
||||
local ID = event.id
|
||||
if ID == 4 then -- landed
|
||||
if ID == 4 or ID == 55 then -- landed, runway touch
|
||||
if csarManager.verbose then
|
||||
trigger.action.outText("land event " .. ID .. "received.", 30)
|
||||
end
|
||||
csarManager.heloLanded(theUnit)
|
||||
end
|
||||
|
||||
if ID == 3 or ID == 55 then -- take off, postponed take-off
|
||||
if ID == 3 or ID == 54 then -- take off, runway take-off
|
||||
csarManager.heloDeparted(theUnit)
|
||||
end
|
||||
|
||||
@ -354,18 +366,15 @@ function csarManager:onEvent(event)
|
||||
csarManager.setCommsMenu(theUnit)
|
||||
-- we also need to make sure that there are no
|
||||
-- more troopsOnBoard
|
||||
|
||||
local myName = theUnit:getName()
|
||||
local conf = csarManager.getUnitConfig(theUnit)
|
||||
conf.unit = theUnit
|
||||
conf.troopsOnBoard = {}
|
||||
local totalMass = cargoSuper.calculateTotalMassFor(myName)
|
||||
|
||||
-- now also set cargo weight for the unit
|
||||
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
|
||||
totalMass = cargoSuper.calculateTotalMassFor(myName)
|
||||
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -698,6 +707,8 @@ end
|
||||
|
||||
|
||||
function csarManager.setCommsMenu(theUnit)
|
||||
-- add menu for this aircraft/group when it spawns
|
||||
-- compatible with dynamic spawns for DCS 2.9.6
|
||||
if not theUnit then return end
|
||||
if not theUnit:isExist() then return end
|
||||
|
||||
@ -821,11 +832,19 @@ function csarManager.doListCSARRequests(args)
|
||||
status = status .. " [" .. delta .. "]" -- remove me
|
||||
end
|
||||
end
|
||||
local locinfo = ""
|
||||
if twn and towns then
|
||||
local village, data, dist = twn.closestTownTo(mission.zone.point)
|
||||
dist = dist * 0.539957 -- nm conversion
|
||||
dist = math.floor(dist/100) / 10
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(mission.zone.point, data.p)
|
||||
locinfo = ", " .. dist .. "nm " .. bear .. " of " .. village
|
||||
end
|
||||
if csarManager.vectoring then
|
||||
report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..mission.dist .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
|
||||
report = report .. "\n".. mission.name .. locinfo .. ", bearing " .. b .. ", " ..mission.dist .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
|
||||
else
|
||||
-- leave out vectoring
|
||||
report = report .. "\n".. mission.name .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
|
||||
report = report .. "\n".. mission.name .. locinfo .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "3.0.9"
|
||||
dcsCommon.version = "3.1.2"
|
||||
--[[-- VERSION HISTORY
|
||||
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
|
||||
- point2text new intsOnly option
|
||||
@ -26,7 +26,9 @@ dcsCommon.version = "3.0.9"
|
||||
3.0.9 - new getOrigPositionByID()
|
||||
- unitName2ID[] reverse lookup
|
||||
- unitName2Heading
|
||||
|
||||
3.1.0 - updates to events, DCS update 7-11 2024 hardening
|
||||
3.1.1 - added Chinook to troop carriers
|
||||
3.1.2 - isTroopCarrier() hardening against DCS sillieness
|
||||
--]]--
|
||||
|
||||
-- dcsCommon is a library of common lua functions
|
||||
@ -39,7 +41,7 @@ dcsCommon.version = "3.0.9"
|
||||
|
||||
-- globals
|
||||
dcsCommon.cbID = 0 -- callback id for simple callback scheduling
|
||||
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D"} -- Ka-50, Apache and Gazelle can't carry troops
|
||||
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D", "CH-47Fbl1"} -- Ka-50, Apache and Gazelle can't carry troops
|
||||
dcsCommon.coalitionSides = {0, 1, 2}
|
||||
dcsCommon.maxCountry = 86 -- number of countries defined in total
|
||||
|
||||
@ -707,7 +709,7 @@ dcsCommon.version = "3.0.9"
|
||||
end
|
||||
|
||||
function dcsCommon.compassPositionOfARelativeToB(A, B)
|
||||
-- warning: is REVERSE in order for bearing, returns a string like 'Sorth', 'Southwest'
|
||||
-- warning: is REVERSE in order for bearing, returns a string like 'North', 'Southwest'
|
||||
if not A then return "***error:A***" end
|
||||
if not B then return "***error:B***" end
|
||||
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360
|
||||
@ -2566,9 +2568,10 @@ end
|
||||
"Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
|
||||
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
|
||||
"unit task timeout", "unit task stage", -- 50
|
||||
"subtask score", "extra score", "mission restart", "winner",
|
||||
"postponed takeoff", "postponed land", -- 56
|
||||
"max"}
|
||||
"subtask score", "mission restart", "winner", -- 53
|
||||
"runway takeoff", "runway touchdown", "LMS Restart", -- 56
|
||||
"sim freeze", "sum unfreeze", "player start repair", "player end repair", --60
|
||||
"max",} -- 61
|
||||
if id > #events then return "Unknown (ID=" .. id .. ")" end
|
||||
return events[id]
|
||||
end
|
||||
@ -2839,9 +2842,9 @@ end
|
||||
function dcsCommon.isTroopCarrier(theUnit, carriers)
|
||||
-- return true if conf can carry troups
|
||||
if not theUnit then return false end
|
||||
|
||||
if not theUnit.getTypeName then return false end -- hardening against DCS sillieness
|
||||
-- see if carriers contains "helo" and theUnit is a helo
|
||||
if dcsCommon.arrayContainsString(carriers, "helo") or dcsCommon.arrayContainsString(carriers, "helos")then
|
||||
if dcsCommon.arrayContainsString(carriers, "helo") or dcsCommon.arrayContainsString(carriers, "helos") then
|
||||
local grp = theUnit:getGroup()
|
||||
if grp:getCategory() == 1 then -- NOT category bug prone, is a group check
|
||||
return true
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxHeloTroops = {}
|
||||
cfxHeloTroops.version = "3.0.4"
|
||||
cfxHeloTroops.version = "3.1.0"
|
||||
cfxHeloTroops.verbose = false
|
||||
cfxHeloTroops.autoDrop = true
|
||||
cfxHeloTroops.autoPickup = false
|
||||
@ -8,31 +8,6 @@ cfxHeloTroops.requestRange = 500 -- meters
|
||||
--
|
||||
--[[--
|
||||
VERSION HISTORY
|
||||
1.1.3 - repaired forgetting 'wait-' when loading/disembarking
|
||||
1.1.4 - corrected coalition bug in deployTroopsFromHelicopter
|
||||
2.0.0 - added weight change when troops enter and leave the helicopter
|
||||
- idividual troop capa max per helicopter
|
||||
2.0.1 - lib loader verification
|
||||
- uses dcsCommon.isTroopCarrier(theUnit)
|
||||
2.0.2 - can now deploy from spawners with "requestable" attribute
|
||||
2.1.0 - supports config zones
|
||||
- check spawner legality by types
|
||||
- updated types to include 2.7.6 additions to infantry
|
||||
- updated types to include stinger/manpads
|
||||
2.2.0 - minor maintenance (dcsCommon)
|
||||
- (re?) connected readConfigZone (wtf?)
|
||||
- persistence support
|
||||
- made legalTroops entrirely optional and defer to dcsComon else
|
||||
2.3.0 - interface with owned zones and playerScore when
|
||||
- combat-dropping troops into non-owned owned zone.
|
||||
- prevent auto-load from pre-empting loading csar troops
|
||||
2.3.1 - added ability to self-define troopCarriers via config
|
||||
2.4.0 - added missing support for attackZone orders (destination)
|
||||
- eliminated cfxPlayer module import and all dependencies
|
||||
- added support for groupTracker / limbo
|
||||
- removed restriction to only apply to helicopters in anticipation of the C-130 Hercules appearing in the game
|
||||
2.4.1 - new actionSound attribute, sound plays to group whenever
|
||||
troops have boarded or disembarked
|
||||
3.0.0 - added requestable cloner support
|
||||
- harmonized spawning invocations across cloners and spawners
|
||||
- dmlZones
|
||||
@ -41,14 +16,10 @@ cfxHeloTroops.requestRange = 500 -- meters
|
||||
3.0.2 - fixed a typo in in-air menu
|
||||
3.0.3 - pointInZone check for insertion rather than radius
|
||||
3.0.4 - also handles picking up troops with orders "captureandhold"
|
||||
3.0.5 - worked around a new issues accessing a unit's name
|
||||
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
|
||||
|
||||
--]]--
|
||||
--
|
||||
-- cfxHeloTroops -- a module to pick up and drop infantry.
|
||||
-- Can be used with ANY aircraft, configured by default to be
|
||||
-- restricted to troop-carrying helicopters.
|
||||
-- might be configure to apply to any type you want using the
|
||||
-- configuration zone.
|
||||
|
||||
|
||||
cfxHeloTroops.requiredLibs = {
|
||||
@ -284,9 +255,8 @@ function cfxHeloTroops.addConfigMenu(conf)
|
||||
end
|
||||
|
||||
function cfxHeloTroops.setCommsMenu(theUnit)
|
||||
-- depending on own load state, we set the command structure
|
||||
-- it begins at 10-other, and has 'Assault Troops' as main menu with submenus
|
||||
-- as required
|
||||
-- compatible with DCS 2.9.6 dynamic spawns
|
||||
-- set F10 Other.. menu for group
|
||||
if not theUnit then return end
|
||||
if not theUnit:isExist() then return end
|
||||
|
||||
@ -301,7 +271,7 @@ function cfxHeloTroops.setCommsMenu(theUnit)
|
||||
local group = theUnit:getGroup()
|
||||
local id = group:getID()
|
||||
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
||||
conf.id = id; -- we do this ALWAYS to it is current even after a crash
|
||||
conf.id = id; -- we ALWAYS do this so it is current even after a crash
|
||||
conf.unit = theUnit -- link back
|
||||
|
||||
-- ok, first, if we don't have an F-10 menu, create one
|
||||
@ -819,10 +789,10 @@ function cfxHeloTroops:onEvent(theEvent)
|
||||
local initiator = theEvent.initiator
|
||||
if not initiator then return end -- not interested
|
||||
local theUnit = initiator
|
||||
local name = theUnit:getName()
|
||||
-- see if this is a player aircraft
|
||||
if not theUnit.getPlayerName then return end -- not a player
|
||||
if not theUnit:getPlayerName() then return end -- not a player
|
||||
local name = theUnit:getName() -- moved to a later
|
||||
|
||||
-- only for helicopters -- overridedden by troop carriers
|
||||
-- we don't check for cat any more, so any airframe
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
jtacGrpUI = {}
|
||||
jtacGrpUI.version = "3.0.0"
|
||||
jtacGrpUI.version = "3.1.0"
|
||||
jtacGrpUI.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones",
|
||||
@ -12,6 +12,7 @@ jtacGrpUI.requiredLibs = {
|
||||
- clean-up
|
||||
- jtacSound
|
||||
3.0.0 - support for attachTo:
|
||||
3.1.0 - support for DCS 2.0.6 dynamic player spwans
|
||||
|
||||
--]]--
|
||||
-- find & command cfxGroundTroops-based jtacs
|
||||
@ -151,7 +152,7 @@ function jtacGrpUI.setCommsMenu(theGroup)
|
||||
end
|
||||
|
||||
local conf = jtacGrpUI.getConfigForGroup(theGroup)
|
||||
conf.id = theGroup:getID(); -- we always do this ALWAYS
|
||||
conf.id = theGroup:getID(); -- we always do this
|
||||
|
||||
if jtacGrpUI.simpleCommands then
|
||||
-- we install directly in F-10 other
|
||||
@ -294,25 +295,29 @@ function jtacGrpUI:onEvent(theEvent)
|
||||
if not theEvent then return end
|
||||
local theUnit = theEvent.initiator
|
||||
if not theUnit then return end
|
||||
if not theUnit.getName then return end -- dcs 2.9.6 jul-11 fix
|
||||
local uName = theUnit:getName()
|
||||
if not theUnit.getPlayerName then return end
|
||||
if not theUnit:getPlayerName() then return end
|
||||
-- we now have a player birth event.
|
||||
local pName = theUnit:getPlayerName()
|
||||
local theGroup = theUnit:getGroup()
|
||||
if not theGroup then return end
|
||||
local gName = theGroup:getName()
|
||||
if not gName then return end
|
||||
if jtacGrpUI.verbose then
|
||||
trigger.action.outText("+++jGUI: birth player. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30)
|
||||
local id = theEvent.id
|
||||
if id == 15 then
|
||||
-- we now have a player birth event.
|
||||
local pName = theUnit:getPlayerName()
|
||||
local theGroup = theUnit:getGroup()
|
||||
if not theGroup then return end
|
||||
local gName = theGroup:getName()
|
||||
if not gName then return end
|
||||
if jtacGrpUI.verbose then
|
||||
trigger.action.outText("+++jGUI: birth player. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30)
|
||||
end
|
||||
local conf = jtacGrpUI.getConfigByGroupName(gName)
|
||||
if conf then
|
||||
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
|
||||
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
|
||||
end
|
||||
|
||||
jtacGrpUI.setCommsMenu(theGroup)
|
||||
end
|
||||
local conf = jtacGrpUI.getConfigByGroupName(gName)
|
||||
if conf then
|
||||
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
|
||||
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
|
||||
end
|
||||
|
||||
jtacGrpUI.setCommsMenu(theGroup)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
messenger = {}
|
||||
messenger.version = "3.1.0"
|
||||
messenger.version = "3.2.0"
|
||||
messenger.verbose = false
|
||||
messenger.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -13,6 +13,8 @@ messenger.messengers = {}
|
||||
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
|
||||
3.1.0 - msgGroup supports multiple groups, separated by comma
|
||||
- msgUnit supports multiple units, separated by comma
|
||||
3.2.0 - loc and twn wildcard support (inherited from zones)
|
||||
|
||||
--]]--
|
||||
|
||||
function messenger.addMessenger(theZone)
|
||||
@ -165,7 +167,7 @@ end
|
||||
|
||||
|
||||
--
|
||||
-- reat attributes
|
||||
-- read attributes
|
||||
--
|
||||
function messenger.createMessengerWithZone(theZone)
|
||||
local aMessage = theZone:getStringFromZoneProperty("message", "")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
ownAll = {}
|
||||
ownAll.version = "1.0.0"
|
||||
ownAll.version = "1.1.0"
|
||||
ownAll.verbose = false
|
||||
ownAll.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -9,7 +9,7 @@ ownAll.requiredLibs = {
|
||||
--[[--
|
||||
VERSION HISTORY
|
||||
- 1.0.0 - Initial version
|
||||
|
||||
- 1.1.0 - dml:masterOwner / getCoalition() updates
|
||||
--]]--
|
||||
|
||||
ownAll.zones = {}
|
||||
@ -69,13 +69,13 @@ function ownAll.calcState(theZone)
|
||||
local blueNum = 0
|
||||
local allSame = true
|
||||
if #theZone.zones < 1 then return -1, 0, 0 end
|
||||
local s = theZone.zones[1].owner
|
||||
local s = theZone.zones[1]:getCoalition() -- owner
|
||||
if not s then
|
||||
trigger.action.outText("+++oAll: zone <" .. theZone.zones[1].name .."> has no owner (?)", 30)
|
||||
s = -1
|
||||
end
|
||||
for idx, aZone in pairs (theZone.zones) do
|
||||
local s2 = aZone.owner
|
||||
local s2 = aZone:getCoalition() -- .owner
|
||||
if not s2 then
|
||||
trigger.action.outText("+++oAll: zone <" .. aZone.name .."> has no owner (?)", 30)
|
||||
s2 = -1
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxOwnedZones = {}
|
||||
cfxOwnedZones.version = "2.3.1"
|
||||
cfxOwnedZones.version = "2.4.0"
|
||||
cfxOwnedZones.verbose = false
|
||||
cfxOwnedZones.announcer = true
|
||||
cfxOwnedZones.name = "cfxOwnedZones"
|
||||
@ -43,6 +43,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
||||
- title attribute
|
||||
- code clean-up
|
||||
2.3.1 - restored getNearestOwnedZoneToPoint
|
||||
2.4.0 - dmlZones masterOwner update
|
||||
|
||||
--]]--
|
||||
cfxOwnedZones.requiredLibs = {
|
||||
"dcsCommon",
|
||||
@ -180,6 +182,7 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
||||
aZone.neutralFill = aZone:getRGBAVectorFromZoneProperty("neutralFill", cfxOwnedZones.neutralFill)
|
||||
|
||||
-- masterOwner
|
||||
--[[--
|
||||
if aZone:hasProperty("masterOwner") then
|
||||
local masterZone = aZone:getStringFromZoneProperty("masterOwner", "cfxNoneErr")
|
||||
local theMaster = cfxZones.getZoneByName(masterZone)
|
||||
@ -193,6 +196,7 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
||||
end
|
||||
end
|
||||
end
|
||||
--]]--
|
||||
|
||||
aZone.announcer = aZone:getBoolFromZoneProperty("announcer", cfxZones.announcer)
|
||||
if aZone:hasProperty("announce") then
|
||||
@ -348,7 +352,7 @@ function cfxOwnedZones.update()
|
||||
for idz, theZone in pairs(cfxOwnedZones.zones) do
|
||||
theZone.numRed = 0
|
||||
theZone.numBlue = 0
|
||||
local lastOwner = theZone.owner
|
||||
local lastOwner = theZone.owner -- do NOT use dml:getCoalition()!
|
||||
if not lastOwner then
|
||||
trigger.action.outText("+++owdZ: WARNING - zone <" .. theZone.name .. "> has NIL owner", 30)
|
||||
return
|
||||
@ -472,7 +476,7 @@ function cfxOwnedZones.update()
|
||||
-- we do nothing
|
||||
elseif theZone.masterOwner then
|
||||
-- inherit from my master
|
||||
newOwner = theZone.masterOwner.owner
|
||||
newOwner = theZone:getCoalition() -- theZone.masterOwner.owner
|
||||
elseif theZone.numRed < 1 and theZone.numBlue < 1 then
|
||||
-- no troops here. Become neutral?
|
||||
if theZone.numKeep < 1 then
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxPlayerScore = {}
|
||||
cfxPlayerScore.version = "3.3.0"
|
||||
cfxPlayerScore.version = "3.3.1"
|
||||
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
|
||||
cfxPlayerScore.badSound = "Death BRASS.wav"
|
||||
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
|
||||
@ -16,6 +16,8 @@ cfxPlayerScore.firstSave = true -- to force overwrite
|
||||
3.1.0 - shared data for persistence
|
||||
3.2.0 - integration with bank
|
||||
3.3.0 - case INsensitivity for all typeScore objects
|
||||
3.3.1 - fixes for DCS oddity in events after update
|
||||
- cleanup
|
||||
--]]--
|
||||
|
||||
cfxPlayerScore.requiredLibs = {
|
||||
@ -104,15 +106,12 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi
|
||||
if theZone.featNum == 0 then
|
||||
canAward = false
|
||||
end
|
||||
|
||||
if theZone.featType ~= featType then
|
||||
canAward = false
|
||||
end
|
||||
|
||||
if not (theZone.coalition == 0 or theZone.coalition == coa) then
|
||||
canAward = false
|
||||
end
|
||||
|
||||
if featType == "PVP" then
|
||||
-- make sure kill is pvp kill
|
||||
if not victim then canAward = false
|
||||
@ -122,22 +121,17 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi
|
||||
canAward = false
|
||||
end
|
||||
end
|
||||
|
||||
if not cfxZones.pointInZone(loc, theZone) then
|
||||
canAward = false
|
||||
end
|
||||
|
||||
if theZone.ppOnce then
|
||||
if theZone.awardedTo[name] then
|
||||
canAward = false
|
||||
end
|
||||
end
|
||||
|
||||
if canAward then
|
||||
table.insert(theFeats, theZone) -- jupp, add it
|
||||
else
|
||||
table.insert(theFeats, theZone) -- jupp, add it
|
||||
end
|
||||
|
||||
end
|
||||
return theFeats
|
||||
end
|
||||
@ -166,7 +160,11 @@ function cfxPlayerScore.preprocessWildcards(inMsg, aUnit, aVictim)
|
||||
theMsg = theMsg:gsub("<kplayer>", "unknown AI")
|
||||
end
|
||||
end
|
||||
theMsg = theMsg:gsub("<unit>", aVictim:getName())
|
||||
if aVictim.getName then
|
||||
theMsg = theMsg:gsub("<unit>", aVictim:getName())
|
||||
else
|
||||
theMsg = theMsg:gsub("<unit>", "*?*") -- dcs oddity
|
||||
end
|
||||
theMsg = theMsg:gsub("<type>", aVictim:getTypeName())
|
||||
-- victim may not have group. guard against that
|
||||
-- happens if unit 'cooks off'
|
||||
@ -205,16 +203,15 @@ function cfxPlayerScore.cat2BaseScore(inCat)
|
||||
if inCat == 2 then return cfxPlayerScore.ground end -- ground
|
||||
if inCat == 3 then return cfxPlayerScore.ship end -- ship
|
||||
if inCat == 4 then return cfxPlayerScore.train end -- train
|
||||
|
||||
trigger.action.outText("+++scr c2bs: unknown category for lookup: <" .. inCat .. ">, returning 1", 30)
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
|
||||
if not inVictim then return 0 end
|
||||
if not killSide then killSide = -1 end
|
||||
local inName = inVictim:getName()
|
||||
local inName
|
||||
if inVictim.getName then inName = inVictim:getName() else inName = "*?*" end -- dcs oddity
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++PScr: ob2sc entry to resolve name <" .. inName .. ">", 30)
|
||||
end
|
||||
@ -251,13 +248,10 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
|
||||
objectScore = cfxPlayerScore.typeScore[theType:upper()]
|
||||
end
|
||||
end
|
||||
|
||||
if type(objectScore) == "string" then
|
||||
objectScore = tonumber(objectScore)
|
||||
end
|
||||
|
||||
if objectScore then return objectScore end
|
||||
|
||||
-- we now try and get the general type of the killed object
|
||||
local desc = inVictim:getDesc() -- Object.getDesc(inVictim)
|
||||
local attributes = desc.attributes
|
||||
@ -268,7 +262,6 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
|
||||
if attributes["Ships"] then return cfxPlayerScore.ship end
|
||||
-- trains can't be detected
|
||||
end
|
||||
|
||||
if not objectScore then return 0 end
|
||||
return objectScore
|
||||
end
|
||||
@ -277,34 +270,29 @@ function cfxPlayerScore.unit2score(inUnit)
|
||||
local vicGroup = inUnit:getGroup()
|
||||
local vicCat = vicGroup:getCategory()-- group cat, not 2.9 affected
|
||||
local vicType = inUnit:getTypeName()
|
||||
local vicName = inUnit:getName()
|
||||
local vicName
|
||||
if inUnit.getName then vicName = inUnit:getName() else vicName = "*?*" end
|
||||
if type(vicName) == "number" then vicName = tostring(vicName) end
|
||||
|
||||
-- simply extend by adding items to the typescore table.concat
|
||||
-- we first try by unit name. This allows individual
|
||||
-- named hi-value targets to have individual scores
|
||||
local uScore = cfxPlayerScore.typeScore[vicName:upper()]
|
||||
|
||||
-- see if all members of group score
|
||||
if (not uScore) and vicGroup then
|
||||
local grpName = vicGroup:getName()
|
||||
uScore = cfxPlayerScore.typeScore[grpName:upper()]
|
||||
end
|
||||
|
||||
if not uScore then
|
||||
-- WE NOW TRY TO ACCESS BY VICTIM'S TYPE STRING
|
||||
uScore = cfxPlayerScore.typeScore[vicType:upper()]
|
||||
else
|
||||
|
||||
end
|
||||
if type(uScore) == "string" then
|
||||
-- convert string to number
|
||||
uScore = tonumber(uScore)
|
||||
end
|
||||
|
||||
if not uScore then uScore = 0 end
|
||||
if uScore > 0 then return uScore end
|
||||
|
||||
-- only apply base scores when the lookup did not give a result
|
||||
uScore = cfxPlayerScore.cat2BaseScore(vicCat)
|
||||
return uScore
|
||||
@ -312,7 +300,7 @@ end
|
||||
|
||||
function cfxPlayerScore.getPlayerScore(playerName)
|
||||
local thePlayerScore = cfxPlayerScore.playerScore[playerName]
|
||||
if thePlayerScore == nil then
|
||||
if not thePlayerScore then
|
||||
thePlayerScore = {}
|
||||
thePlayerScore.name = playerName
|
||||
thePlayerScore.score = 0 -- score
|
||||
@ -379,7 +367,6 @@ function cfxPlayerScore.doLogTypeKill(playerName, thePlayerScore, theType)
|
||||
killCount = killCount + 1
|
||||
thePlayerScore.totalKills = thePlayerScore.totalKills + 1
|
||||
thePlayerScore.killTypes[theType] = killCount
|
||||
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
|
||||
end
|
||||
|
||||
@ -390,14 +377,12 @@ function cfxPlayerScore.logKillForPlayer(playerName, theUnit)
|
||||
if not playerName then return end
|
||||
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
local theType = theUnit:getTypeName()
|
||||
|
||||
if cfxPlayerScore.deferred then
|
||||
-- just queue it
|
||||
table.insert(thePlayerScore.killQueue, theType)
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write-through. why? because it may be a new entry.
|
||||
return
|
||||
end
|
||||
|
||||
cfxPlayerScore.doLogTypeKill(playerName, thePlayerScore, theType)
|
||||
end
|
||||
|
||||
@ -410,9 +395,7 @@ function cfxPlayerScore.doLogFeat(playerName, thePlayerScore, theFeat)
|
||||
featCount = featCount + 1
|
||||
thePlayerScore.totalFeats = thePlayerScore.totalFeats + 1
|
||||
thePlayerScore.featTypes[theFeat] = featCount
|
||||
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
|
||||
|
||||
end
|
||||
|
||||
function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
|
||||
@ -421,34 +404,29 @@ function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
|
||||
if not theFeat then return end
|
||||
if not playerName then return end
|
||||
-- access player's record. will alloc if new by itself
|
||||
|
||||
if coa then
|
||||
local disclaim = ""
|
||||
if cfxPlayerScore.deferred then disclaim = " (award pending)" end
|
||||
trigger.action.outTextForCoalition(coa, playerName .. " achieved " .. theFeat .. disclaim, 30)
|
||||
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
|
||||
end
|
||||
|
||||
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
if cfxPlayerScore.deferred then
|
||||
table.insert(thePlayerScore.featQueue, theFeat)
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
|
||||
return
|
||||
end
|
||||
|
||||
cfxPlayerScore.doLogFeat(playerName, thePlayerScore, theFeat)
|
||||
end
|
||||
|
||||
function cfxPlayerScore.playerScore2text(thePlayerScore, scoreOnly)
|
||||
if not scoreOnly then scoreOnly = false end
|
||||
local desc = thePlayerScore.name .. " statistics:\n"
|
||||
|
||||
if cfxPlayerScore.reportScore then
|
||||
desc = desc .. " - score: ".. thePlayerScore.score .. " - total kills: " .. thePlayerScore.totalKills .. "\n"
|
||||
if scoreOnly then
|
||||
return desc
|
||||
end
|
||||
|
||||
-- now go through all kills
|
||||
desc = desc .. "\nKills by type:\n"
|
||||
if dcsCommon.getSizeOfTable(thePlayerScore.killTypes) < 1 then
|
||||
@ -474,16 +452,13 @@ function cfxPlayerScore.playerScore2text(thePlayerScore, scoreOnly)
|
||||
desc = desc .. "\n"
|
||||
end
|
||||
end
|
||||
|
||||
if cfxPlayerScore.reportScore and thePlayerScore.scoreaccu > 0 then
|
||||
desc = desc .. "\n - unclaimed score: " .. thePlayerScore.scoreaccu .."\n"
|
||||
end
|
||||
|
||||
local featCount = dcsCommon.getSizeOfTable(thePlayerScore.featQueue)
|
||||
if cfxPlayerScore.reportFeats and featCount > 0 then
|
||||
desc = desc .. " - unclaimed feats: " .. featCount .."\n"
|
||||
end
|
||||
|
||||
return desc
|
||||
end
|
||||
|
||||
@ -508,7 +483,6 @@ function cfxPlayerScore.scoreSummaryForPlayersOfCoalition(side)
|
||||
if count < 1 then
|
||||
desc = desc .. " (No score yet)"
|
||||
end
|
||||
|
||||
desc = desc .. "\n"
|
||||
return desc
|
||||
end
|
||||
@ -537,17 +511,14 @@ function cfxPlayerScore.scoreTextForAllPlayers(ranked)
|
||||
isFirst = false
|
||||
rank = rank + 1
|
||||
end
|
||||
|
||||
if dcsCommon.getSizeOfTable(theScores) < 1 then
|
||||
theText = theText .. " (No score yet)\n"
|
||||
end
|
||||
|
||||
if cfxPlayerScore.reportCoalition then
|
||||
--theText = theText .. "\n"
|
||||
theText = theText .. "\nRED total: " .. cfxPlayerScore.coalitionScore[1]
|
||||
theText = theText .. "\nBLUE total: " .. cfxPlayerScore.coalitionScore[2]
|
||||
end
|
||||
|
||||
return theText
|
||||
end
|
||||
|
||||
@ -560,7 +531,11 @@ function cfxPlayerScore.isNamedUnit(theUnit)
|
||||
else
|
||||
-- WARNING: NO EXIST CHECK DONE!
|
||||
-- after kill, unit is dead, so will no longer exist!
|
||||
theName = theUnit:getName()
|
||||
if theUnit.getName then
|
||||
theName = theUnit:getName()
|
||||
else
|
||||
theName = "*?*"
|
||||
end
|
||||
if not theName then return false end
|
||||
end
|
||||
if cfxPlayerScore.typeScore[theName:upper()] then
|
||||
@ -576,9 +551,7 @@ function cfxPlayerScore.awardScoreTo(killSide, theScore, killerName)
|
||||
else
|
||||
playerScore = cfxPlayerScore.updateScoreForPlayer(killerName, theScore)
|
||||
end
|
||||
|
||||
if not cfxPlayerScore.reportScore then return end
|
||||
|
||||
if cfxPlayerScore.announcer then
|
||||
if (theScore > 0) and cfxPlayerScore.deferred then
|
||||
thePlayerRecord = cfxPlayerScore.getPlayerScore(killerName) -- re-read after write
|
||||
@ -614,9 +587,9 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
if theEvent.initiator == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check if this was FORMERLY a player plane
|
||||
local theUnit = theEvent.initiator
|
||||
if not theUnit.getName then return end -- fix for DCS update bug
|
||||
local uName = theUnit:getName()
|
||||
if cfxPlayerScore.unit2player[uName] then
|
||||
-- this requires special IMMEDIATE handling when event is
|
||||
@ -627,19 +600,16 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
theEvent.id == 30 or -- unit loss
|
||||
theEvent.id == 6 then -- eject
|
||||
-- these can lead to a pilot demerit
|
||||
--trigger.action.outText("PREPROC plane player extra event - possible death", 30)
|
||||
-- event does NOT have a player
|
||||
cfxPlayerScore.handlePlayerDeath(theEvent)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- initiator must be player
|
||||
if not theUnit.getPlayerName or
|
||||
not theUnit:getPlayerName() then
|
||||
return false
|
||||
end
|
||||
|
||||
if theEvent.id == 28 then
|
||||
-- we only are interested in kill events where
|
||||
-- there is a target
|
||||
@ -650,20 +620,17 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- if there are kill zones, we filter all kills that happen outside of kill zones
|
||||
if #cfxPlayerScore.killZones > 0 then
|
||||
local pLoc = theUnit:getPoint()
|
||||
local tLoc = theEvent.target:getPoint()
|
||||
local isIn, percent, dist, theZone = cfxZones.pointInOneOfZones(tLoc, cfxPlayerScore.killZones)
|
||||
|
||||
if not isIn then
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: kill detected, but target <" .. theEvent.target:getName() .. "> was outside of any kill zones", 30)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if theZone.duet and not cfxZones.pointInZone(pLoc, theZone) then
|
||||
-- player must be in same zone but was not
|
||||
if cfxPlayerScore.verbose then
|
||||
@ -686,7 +653,7 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
|
||||
-- take off. overwrites timestamp for last landing
|
||||
-- so a blipping t/o does nor count. Pre-proc only
|
||||
if theEvent.id == 3 then
|
||||
if theEvent.id == 3 or theEvent.id == 54 then
|
||||
local now = timer.getTime()
|
||||
local playerName = theUnit:getPlayerName()
|
||||
cfxPlayerScore.lastPlayerLanding[playerName] = now -- overwrite
|
||||
@ -696,7 +663,7 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
-- landing can score. but only the first landing in x seconds
|
||||
-- landing in safe zone promotes any queued scores to
|
||||
-- permanent if enabled, then nils queue
|
||||
if theEvent.id == 4 then
|
||||
if theEvent.id == 4 or theEvent.id == 55 then
|
||||
-- player landed. filter multiple landed events
|
||||
local now = timer.getTime()
|
||||
local playerName = theUnit:getPlayerName()
|
||||
@ -704,7 +671,7 @@ function cfxPlayerScore.preProcessor(theEvent)
|
||||
cfxPlayerScore.lastPlayerLanding[playerName] = now -- overwrite
|
||||
if lastLanding and lastLanding + cfxPlayerScore.delayBetweenLandings > now then
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: Player <" .. playerName .. "> touch-down ignored: too soon.", 30)
|
||||
trigger.action.outText("+++pScr: Player <" .. playerName .. "> touch-down ignored: too soon after last.", 30)
|
||||
trigger.action.outText("now is <" .. now .. ">, between is <" .. cfxPlayerScore.delayBetweenLandings .. ">, last + between is <" .. lastLanding + cfxPlayerScore.delayBetweenLandings .. ">", 30)
|
||||
end
|
||||
-- filter this event
|
||||
@ -731,23 +698,18 @@ function cfxPlayerScore.checkKillFeat(name, killer, victim, fratricide)
|
||||
if not fratricide then fratricide = false end
|
||||
local theLoc = victim:getPoint() -- vic's loc is relevant for zone check
|
||||
local coa = killer:getCoalition()
|
||||
|
||||
local killFeats = cfxPlayerScore.featsForLocation(name, theLoc, coa,"KILL", killer, victim)
|
||||
|
||||
if (not fratricide) and #killFeats > 0 then
|
||||
-- use the feat description
|
||||
-- we may want to use closest, currently simply the first
|
||||
theFeatZone = killFeats[1]
|
||||
local desc = cfxPlayerScore.evalFeatDescription(name, theFeatZone, killer, victim) -- updates awardedTo
|
||||
|
||||
cfxPlayerScore.logFeatForPlayer(name, desc, playerSide)
|
||||
theScore = cfxPlayerScore.getPlayerScore(name) -- re-read after write
|
||||
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("Kill feat awarded/queued for <" .. name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function cfxPlayerScore.killDetected(theEvent)
|
||||
@ -767,7 +729,6 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
|
||||
-- was it a player kill?
|
||||
local pk = dcsCommon.isPlayerUnit(victim)
|
||||
|
||||
-- was it a scenery object?
|
||||
local wasBuilding = dcsCommon.isSceneryObject(victim)
|
||||
if wasBuilding then
|
||||
@ -799,7 +760,9 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
--if not victim.getGroup then
|
||||
if isStO then
|
||||
-- static objects have no group
|
||||
local staticName = victim:getName() -- on statics, this returns
|
||||
local staticName
|
||||
if victim.getName then staticName = victim:getName() -- on statics, this returns
|
||||
else staticName = "*?*" end
|
||||
-- name as entered in TOP LINE
|
||||
local staticScore = cfxPlayerScore.object2score(victim, killSide)
|
||||
|
||||
@ -817,7 +780,6 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
else
|
||||
-- no score, no mentions
|
||||
end
|
||||
|
||||
if not fraternicide then
|
||||
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
|
||||
end
|
||||
@ -834,12 +796,7 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
trigger.action.outText("+++scr: strange stuff:cat, outta here", 30)
|
||||
return
|
||||
end
|
||||
local unitScore = cfxPlayerScore.unit2score(victim)
|
||||
|
||||
-- see which weapon was used. gun kills score 2x
|
||||
-- local killMeth = "" -- meth is currently not defined
|
||||
-- local killWeap = theEvent.weapon -- not supported either
|
||||
|
||||
local unitScore = cfxPlayerScore.unit2score(victim)
|
||||
if pk then -- player kill - add player's name
|
||||
vicDesc = victim:getPlayerName() .. " in " .. vicDesc
|
||||
scoreMod = scoreMod * cfxPlayerScore.pkMod
|
||||
@ -869,17 +826,14 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
trigger.action.outTextForCoalition(killSide, killerName .. " reports killing strategic unit '" .. victim:getName() .. "'", 30)
|
||||
end
|
||||
end
|
||||
|
||||
local totalScore = unitScore * scoreMod
|
||||
-- if the score is negative, awardScoreTo will automatically
|
||||
-- make it immediate, else depending on deferred
|
||||
cfxPlayerScore.awardScoreTo(killSide, totalScore, killerName)
|
||||
|
||||
if not fraternicide then
|
||||
-- only award kill feats for kills of the enemy
|
||||
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
@ -890,13 +844,11 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: Player <" .. playerName .. "> landed", 30)
|
||||
end
|
||||
|
||||
local theScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
|
||||
-- see if a feat is available for this landing
|
||||
local landingFeats = cfxPlayerScore.featsForLocation(playerName, theLoc, playerSide,"LANDING")
|
||||
|
||||
-- first, scheck if landing is awardable, and if so,
|
||||
-- first, check if landing is awardable, and if so,
|
||||
-- award the landing
|
||||
if cfxPlayerScore.landing > 0 or #landingFeats > 0 then
|
||||
-- yes, landings are awarded a score. do before
|
||||
@ -914,7 +866,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
desc = desc .. " aircraft"
|
||||
end
|
||||
end
|
||||
|
||||
cfxPlayerScore.updateScoreForPlayer(playerName, cfxPlayerScore.landing)
|
||||
cfxPlayerScore.logFeatForPlayer(playerName, desc, playerSide)
|
||||
theScore = cfxPlayerScore.getPlayerScore(playerName) -- re-read after write
|
||||
@ -922,7 +873,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
trigger.action.outText("Landing feat awarded/queued for <" .. playerName .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- see if we are using deferred scoring, else can end right now
|
||||
if not cfxPlayerScore.deferred then
|
||||
return
|
||||
@ -930,7 +880,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
-- only continue if there is anything to award
|
||||
local killSize = dcsCommon.getSizeOfTable(theScore.killQueue)
|
||||
local featSize = dcsCommon.getSizeOfTable(theScore.featQueue)
|
||||
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: prepping deferred score for <" .. playerName ..">", 30)
|
||||
end
|
||||
@ -948,15 +897,17 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
if (theZone.owner == coa) or (theZone.owner == 0) or (theZone.owner == nil) then
|
||||
if cfxZones.pointInZone(loc, theZone) then
|
||||
isSafe = true
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, my coa=<" .. coa .. ">, LANDED SAFELY", 30)
|
||||
end
|
||||
end
|
||||
else
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pSc: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, my coa=<" .. coa .. ">, no owner match")
|
||||
if cfxPlayerScore.verbose and cfxZones.pointInZone(loc, theZone) then
|
||||
trigger.action.outText("+++pScr: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, player unit <" .. theUnit:getName() .. ">, my coa=<" .. coa .. ">, no owner match", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not isSafe then
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: deferred, but not inside score safe zone.", 30)
|
||||
@ -975,7 +926,6 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
-- called with player name and unit name in args
|
||||
local playerName = args[1]
|
||||
local unitName = args[2]
|
||||
|
||||
local theUnit = Unit.getByName(unitName)
|
||||
if not theUnit or
|
||||
not Unit.isExist(theUnit)
|
||||
@ -984,18 +934,15 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
trigger.action.outText("Player <" .. playerName .. "> lost score.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
local uid = theUnit:getID()
|
||||
if theUnit:inAir() then
|
||||
trigger.action.outTextForUnit(uid, "Can't award score to <" .. playerName .. ">: unit not on the ground.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
if theUnit:getLife() < 1 then
|
||||
trigger.action.outTextForUnit(uid, "Can't award score to <" .. playerName .. ">: unit did not survive landing.", 30)
|
||||
return -- needs to reslot, don't have to nil player score
|
||||
end
|
||||
|
||||
-- see if player is *still* within a scoreSafe zone
|
||||
local loc = theUnit:getPoint()
|
||||
local coa = theUnit:getCoalition()
|
||||
@ -1008,12 +955,10 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not isSafe then
|
||||
trigger.action.outTextForUnit(uid, "Can't award score for <" .. playerName .. ">, not in safe zone.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
local theScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
local playerSide = dcsCommon.playerName2Coalition(playerName)
|
||||
if playerSide < 1 then
|
||||
@ -1023,14 +968,12 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
if dcsCommon.getSizeOfTable(theScore.killQueue) < 1 and
|
||||
dcsCommon.getSizeOfTable(theScore.featQueue) < 1 and
|
||||
theScore.scoreaccu < 1 then
|
||||
-- player changed planes or
|
||||
-- there was nothing to award
|
||||
-- player changed planes or there was nothing to award
|
||||
trigger.action.outTextForUnit(uid, "Thank you, " .. playerName .. ", no scores or feats pending.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
local hasAward = false
|
||||
|
||||
-- when we get here we award all scores, kills, and feats
|
||||
local desc = "\nPlayer " .. playerName .. " is awarded:\n"
|
||||
-- score and total score
|
||||
@ -1044,8 +987,7 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
end
|
||||
theScore.scoreaccu = 0
|
||||
hasAward = true
|
||||
end
|
||||
|
||||
end
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("Iterating kill q <" .. dcsCommon.getSizeOfTable(theScore.killQueue) .. "> and feat q <" .. dcsCommon.getSizeOfTable(theScore.featQueue) .. ">", 30)
|
||||
end
|
||||
@ -1059,7 +1001,6 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
hasAward = true
|
||||
end
|
||||
theScore.killQueue = {}
|
||||
|
||||
-- iterate feats
|
||||
if dcsCommon.getSizeOfTable(theScore.featQueue) > 0 then
|
||||
desc = desc .. " confirmed feats:\n"
|
||||
@ -1070,11 +1011,9 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
hasAward = true
|
||||
end
|
||||
theScore.featQueue = {}
|
||||
|
||||
if cfxPlayerScore.reportCoalition then
|
||||
desc = desc .. "\nCoalition Total: " .. cfxPlayerScore.coalitionScore[playerSide]
|
||||
end
|
||||
|
||||
-- output score
|
||||
desc = desc .. "\n"
|
||||
if hasAward then
|
||||
@ -1089,11 +1028,9 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
|
||||
-- only counts once
|
||||
local theUnit = theEvent.initiator
|
||||
local uName = theUnit:getName()
|
||||
|
||||
if cfxPlayerScore.verbose then
|
||||
trigger.action.outText("+++pScr: LOA/player death handler entry for <" .. uName .. ">", 30)
|
||||
end
|
||||
|
||||
local pName = cfxPlayerScore.unit2player[uName]
|
||||
if pName then
|
||||
-- this was a player name with link still live.
|
||||
@ -1113,14 +1050,12 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
|
||||
trigger.action.outText("+++pScr - no action for LOA", 30)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function cfxPlayerScore.handlePlayerEvent(theEvent)
|
||||
if theEvent.id == 28 then
|
||||
-- kill from player detected.
|
||||
cfxPlayerScore.killDetected(theEvent)
|
||||
|
||||
elseif theEvent.id == 15 then -- birth
|
||||
-- access player score for player. this will
|
||||
-- allocate if doesn't exist. Any player ever
|
||||
@ -1130,7 +1065,6 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
|
||||
local playerName = thePlayerUnit:getPlayerName()
|
||||
local theScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
-- now re-init feat and score queues
|
||||
|
||||
if theScore.scoreaccu and theScore.scoreaccu > 0 then
|
||||
trigger.action.outTextForCoalition(playerSide, "Player " .. playerName .. ", score of <" .. theScore.scoreaccu .. "> points discarded.", 30)
|
||||
end
|
||||
@ -1146,9 +1080,10 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
|
||||
-- write back
|
||||
cfxPlayerScore.setPlayerScore(playerName, theScore)
|
||||
|
||||
elseif theEvent.id == 4 then -- land
|
||||
elseif theEvent.id == 4 or theEvent.id == 55 then -- land
|
||||
-- see if plane is still connected to player
|
||||
local theUnit = theEvent.initiator
|
||||
if not theUnit.getName then return end -- dcs oddity precaution
|
||||
local uName = theUnit:getName()
|
||||
if cfxPlayerScore.unit2player[uName] then
|
||||
-- is filtered if too soon after last take-off/landing
|
||||
@ -1158,7 +1093,6 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
|
||||
trigger.action.outText("+++pScr: filtered landing for <" .. uName .. ">: player no longer linked to unit", 30)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -1167,69 +1101,51 @@ function cfxPlayerScore.readConfigZone(theZone)
|
||||
-- default scores
|
||||
cfxPlayerScore.aircraft = theZone:getNumberFromZoneProperty("aircraft", 50)
|
||||
cfxPlayerScore.helo = theZone:getNumberFromZoneProperty("helo", 40)
|
||||
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
|
||||
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
|
||||
cfxPlayerScore.ship = theZone:getNumberFromZoneProperty("ship", 80)
|
||||
cfxPlayerScore.train = theZone:getNumberFromZoneProperty( "train", 5)
|
||||
cfxPlayerScore.landing = theZone:getNumberFromZoneProperty("landing", 0) -- if > 0 then feat
|
||||
|
||||
cfxPlayerScore.pkMod = theZone:getNumberFromZoneProperty( "pkMod", 1) -- factor for killing a player
|
||||
cfxPlayerScore.ffMod = theZone:getNumberFromZoneProperty( "ffMod", -2) -- factor for friendly fire
|
||||
cfxPlayerScore.planeLoss = theZone:getNumberFromZoneProperty("planeLoss", -10) -- points added when player's plane crashes
|
||||
|
||||
cfxPlayerScore.announcer = theZone:getBoolFromZoneProperty("announcer", true)
|
||||
|
||||
if theZone:hasProperty("badSound") then
|
||||
cfxPlayerScore.badSound = theZone:getStringFromZoneProperty("badSound", "<nosound>")
|
||||
end
|
||||
if theZone:hasProperty("scoreSound") then
|
||||
cfxPlayerScore.scoreSound = theZone:getStringFromZoneProperty("scoreSound", "<nosound>")
|
||||
end
|
||||
|
||||
-- triggering saving scores
|
||||
if theZone:hasProperty("saveScore?") then
|
||||
cfxPlayerScore.saveScore = theZone:getStringFromZoneProperty("saveScore?", "none")
|
||||
cfxPlayerScore.lastSaveScore = trigger.misc.getUserFlag(cfxPlayerScore.saveScore)
|
||||
cfxPlayerScore.incremental = theZone:getBoolFromZoneProperty("incremental", false) -- incremental saves
|
||||
end
|
||||
|
||||
-- triggering show all scores
|
||||
if theZone:hasProperty("showScore?") then
|
||||
cfxPlayerScore.showScore = theZone:getStringFromZoneProperty("showScore?", "none")
|
||||
cfxPlayerScore.lastShowScore = trigger.misc.getUserFlag(cfxPlayerScore.showScore)
|
||||
end
|
||||
|
||||
cfxPlayerScore.rankPlayers = theZone:getBoolFromZoneProperty("rankPlayers", false)
|
||||
|
||||
cfxPlayerScore.scoreOnly = theZone:getBoolFromZoneProperty("scoreOnly", true)
|
||||
|
||||
cfxPlayerScore.deferred = theZone:getBoolFromZoneProperty("deferred", false)
|
||||
|
||||
cfxPlayerScore.delayAfterLanding = theZone:getNumberFromZoneProperty("delayAfterLanding", 10)
|
||||
|
||||
cfxPlayerScore.scoreFileName = theZone:getStringFromZoneProperty("scoreFileName", "Player Scores")
|
||||
|
||||
cfxPlayerScore.reportScore = theZone:getBoolFromZoneProperty("reportScore", true)
|
||||
|
||||
cfxPlayerScore.reportFeats = theZone:getBoolFromZoneProperty("reportFeats", true)
|
||||
|
||||
cfxPlayerScore.reportCoalition = theZone:getBoolFromZoneProperty("reportCoalition", false) -- also show coalition score
|
||||
|
||||
cfxPlayerScore.noGrief = theZone:getBoolFromZoneProperty( "noGrief", true) -- noGrief = only add positive score
|
||||
|
||||
if theZone:hasProperty("redScore#") then
|
||||
cfxPlayerScore.redScoreOut = theZone:getStringFromZoneProperty("redScore#")
|
||||
theZone:setFlagValue(cfxPlayerScore.redScoreOut, cfxPlayerScore.coalitionScore[1])
|
||||
end
|
||||
|
||||
if theZone:hasProperty("blueScore#") then
|
||||
cfxPlayerScore.blueScoreOut = theZone:getStringFromZoneProperty("blueScore#")
|
||||
theZone:setFlagValue(cfxPlayerScore.blueScoreOut, cfxPlayerScore.coalitionScore[2])
|
||||
end
|
||||
|
||||
if theZone:hasProperty("sharedData") then
|
||||
cfxPlayerScore.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
|
||||
end
|
||||
|
||||
cfxPlayerScore.score2finance = theZone:getNumberFromZoneProperty("score2finance", 1) -- factor to convert points to bank finance
|
||||
end
|
||||
|
||||
@ -1287,11 +1203,9 @@ function cfxPlayerScore.loadData()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- save scores (text file)
|
||||
--
|
||||
|
||||
function cfxPlayerScore.saveScores(theText, name)
|
||||
if not _G["persistence"] then
|
||||
trigger.action.outText("+++pScr: persistence module required to save scores. Here are the scores that I would have saved to <" .. name .. ">:\n", 30)
|
||||
@ -1331,7 +1245,6 @@ function cfxPlayerScore.saveScoreToFile()
|
||||
-- local built score table
|
||||
local ranked = cfxPlayerScore.rankPlayers
|
||||
local theText = cfxPlayerScore.scoreTextForAllPlayers(ranked)
|
||||
|
||||
-- save to disk
|
||||
cfxPlayerScore.saveScores(theText, cfxPlayerScore.scoreFileName)
|
||||
end
|
||||
@ -1341,14 +1254,12 @@ function cfxPlayerScore.showScoreToAll()
|
||||
local theText = cfxPlayerScore.scoreTextForAllPlayers(ranked)
|
||||
trigger.action.outText(theText, 30)
|
||||
end
|
||||
|
||||
--
|
||||
-- Update
|
||||
--
|
||||
function cfxPlayerScore.update()
|
||||
-- re-invoke in 1 second
|
||||
timer.scheduleFunction(cfxPlayerScore.update, {}, timer.getTime() + 1)
|
||||
|
||||
-- see if someone banged on saveScore
|
||||
if cfxPlayerScore.saveScore then
|
||||
if cfxZones.testZoneFlag(cfxPlayerScore, cfxPlayerScore.saveScore, "change", "lastSaveScore") then
|
||||
@ -1358,7 +1269,6 @@ function cfxPlayerScore.update()
|
||||
cfxPlayerScore.saveScoreToFile()
|
||||
end
|
||||
end
|
||||
|
||||
-- showScore perhaps?
|
||||
if cfxPlayerScore.showScore then
|
||||
if cfxZones.testZoneFlag(cfxPlayerScore, cfxPlayerScore.showScore, "change", "lastShowScore") then
|
||||
@ -1368,7 +1278,6 @@ function cfxPlayerScore.update()
|
||||
cfxPlayerScore.showScoreToAll()
|
||||
end
|
||||
end
|
||||
|
||||
-- check score flags
|
||||
if cfxPlayerScore.blueTriggerFlags then
|
||||
local coa = 2
|
||||
@ -1377,13 +1286,11 @@ function cfxPlayerScore.update()
|
||||
if tVal ~= newVal then
|
||||
-- score!
|
||||
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName]
|
||||
cfxPlayerScore.blueTriggerFlags[tName] = newVal
|
||||
|
||||
cfxPlayerScore.blueTriggerFlags[tName] = newVal
|
||||
if cfxPlayerScore.announcer then
|
||||
trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
|
||||
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
|
||||
end
|
||||
|
||||
-- bank it if exists
|
||||
local amount
|
||||
if bank and bank.addFunds then
|
||||
@ -1402,17 +1309,12 @@ function cfxPlayerScore.update()
|
||||
local newVal = trigger.misc.getUserFlag(tName)
|
||||
if tVal ~= newVal then
|
||||
-- score!
|
||||
|
||||
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName]
|
||||
cfxPlayerScore.redTriggerFlags[tName] = newVal
|
||||
--if bank and bank.addFunds then
|
||||
-- bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
|
||||
--end
|
||||
if cfxPlayerScore.announcer then
|
||||
trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
|
||||
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
|
||||
end
|
||||
|
||||
-- bank it if exists
|
||||
local amount
|
||||
if bank and bank.addFunds then
|
||||
@ -1429,7 +1331,6 @@ function cfxPlayerScore.update()
|
||||
if cfxPlayerScore.redScoreOut then
|
||||
cfxZones.setFlagValue(cfxPlayerScore.redScoreOut, cfxPlayerScore.coalitionScore[1], cfxPlayerScore)
|
||||
end
|
||||
|
||||
if cfxPlayerScore.blueScoreOut then
|
||||
cfxZones.setFlagValue(cfxPlayerScore.blueScoreOut, cfxPlayerScore.coalitionScore[2], cfxPlayerScore)
|
||||
end
|
||||
@ -1448,19 +1349,8 @@ function cfxPlayerScore.start()
|
||||
-- identify and process a score table zones
|
||||
local theZone = cfxZones.getZoneByName("playerScoreTable")
|
||||
if theZone then
|
||||
-- trigger.action.outText("Reading custom player score table", 30)
|
||||
-- read all into my types registry, replacing whatever is there
|
||||
cfxPlayerScore.typeScore = theZone:getAllZoneProperties(true) -- true = get all properties in UPPER case
|
||||
-- local n = dcsCommon.getSizeOfTable(cfxPlayerScore.typeScore)
|
||||
-- trigger.action.outText("Table has <" .. n .. "> entries:", 30)
|
||||
if true then
|
||||
--trigger.action.outText("Custom PlayerScore Type Score Table:", 30)
|
||||
for name, val in pairs (cfxPlayerScore.typeScore) do
|
||||
-- trigger.action.outText("ps[" .. name .. "]=<" .. val .. ">", 30)
|
||||
end
|
||||
end
|
||||
else
|
||||
--trigger.action.outText("No custom score defined", 30)
|
||||
end
|
||||
|
||||
-- read score tiggers and values
|
||||
@ -1493,43 +1383,37 @@ function cfxPlayerScore.start()
|
||||
end
|
||||
cfxPlayerScore.blueTriggerFlags[tName] = trigger.misc.getUserFlag(tName)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
-- now read my config zone
|
||||
local theZone = cfxZones.getZoneByName("playerScoreConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("playerScoreConfig")
|
||||
end
|
||||
cfxPlayerScore.readConfigZone(theZone)
|
||||
|
||||
-- read all scoreSafe zones
|
||||
local safeZones = cfxZones.zonesWithProperty("scoreSafe")
|
||||
for k, aZone in pairs(safeZones) do
|
||||
cfxPlayerScore.addSafeZone(aZone)
|
||||
end
|
||||
|
||||
-- read all feat zones
|
||||
local featZones = cfxZones.zonesWithProperty("feat")
|
||||
for k, aZone in pairs(featZones) do
|
||||
cfxPlayerScore.addFeatZone(aZone)
|
||||
end
|
||||
|
||||
end
|
||||
-- read all kill zones
|
||||
local killZones = cfxZones.zonesWithProperty("killZone")
|
||||
for k, aZone in pairs(killZones) do
|
||||
cfxPlayerScore.addKillZone(aZone)
|
||||
end
|
||||
|
||||
-- check that deferred has scoreSafe zones
|
||||
if cfxPlayerScore.deferred and dcsCommon.getSizeOfTable(cfxPlayerScore.safeZones) < 1 then
|
||||
trigger.action.outText("+++pScr: WARNING - deferred scoring active but no 'scoreSafe' zones set!", 30)
|
||||
end
|
||||
|
||||
|
||||
-- subscribe to events and use dcsCommon's handler structure
|
||||
dcsCommon.addEventHandler(cfxPlayerScore.handlePlayerEvent,
|
||||
cfxPlayerScore.preProcessor,
|
||||
cfxPlayerScore.postProcessor)
|
||||
|
||||
cfxPlayerScore.postProcessor)
|
||||
-- now load all save data and populate map with troops that
|
||||
-- we deployed last save.
|
||||
if persistence then
|
||||
@ -1543,7 +1427,6 @@ function cfxPlayerScore.start()
|
||||
|
||||
-- start update
|
||||
cfxPlayerScore.update()
|
||||
|
||||
trigger.action.outText("cfxPlayerScore v" .. cfxPlayerScore.version .. " started", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxPlayerScoreUI = {}
|
||||
cfxPlayerScoreUI.version = "2.1.0"
|
||||
cfxPlayerScoreUI.version = "3.0.0"
|
||||
cfxPlayerScoreUI.verbose = false
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
@ -11,6 +11,7 @@ cfxPlayerScoreUI.verbose = false
|
||||
- score summary for side
|
||||
- allowAll
|
||||
- 2.1.1 - minor cleanup
|
||||
- 3.0.0 - compatible with dynamic groups/units in DCS 2.9.6
|
||||
|
||||
--]]--
|
||||
cfxPlayerScoreUI.requiredLibs = {
|
||||
@ -19,7 +20,7 @@ cfxPlayerScoreUI.requiredLibs = {
|
||||
"cfxPlayerScore",
|
||||
}
|
||||
cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav"
|
||||
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft
|
||||
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft. stores command roots
|
||||
cfxPlayerScoreUI.allowAll = true
|
||||
cfxPlayerScoreUI.ranked = true
|
||||
|
||||
|
||||
@ -1,30 +1,23 @@
|
||||
radioMenu = {}
|
||||
radioMenu.version = "3.0.0"
|
||||
radioMenu.version = "4.0.0"
|
||||
radioMenu.verbose = false
|
||||
radioMenu.ups = 1
|
||||
radioMenu.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
-- note: cfxMX is optional unless using types or groups attributes
|
||||
radioMenu.menus = {}
|
||||
radioMenu.mainMenus = {} -- dict
|
||||
radioMenu.lateGroups = {} -- dict by ID
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
2.1.0 - valA/valB/valC/valD attributes
|
||||
OOP cfxZones
|
||||
corrected CD setting for "D"
|
||||
ackA, ackB, ackC, ackD attributes
|
||||
valA-D now define full method, not just values
|
||||
full wildcard support for ack and cooldown
|
||||
2.1.1 - outMessage now works correctly
|
||||
2.2.0 - clean-up
|
||||
2.2.1 - corrected ackD
|
||||
2.3.0 - added wildcard "*" ability for group name match
|
||||
- added ackASnd .. ackDSnd sounds as options
|
||||
3.0.0 - new radioMainMenu and attachTo: mechanics
|
||||
cascading radioMainMenu support
|
||||
detect cyclic references
|
||||
detect cyclic references
|
||||
4.0.0 - added infrastructure for dynamic players (DCS 2.9.6)
|
||||
- added dynamic player support
|
||||
--]]--
|
||||
|
||||
function radioMenu.addRadioMenu(theZone)
|
||||
@ -53,15 +46,33 @@ end
|
||||
--
|
||||
-- read zone
|
||||
--
|
||||
function radioMenu.lateFilterPlayerForType(theUnit, theZone)
|
||||
-- returns true if theUnit matches zone's group criterium
|
||||
-- false otherwise
|
||||
if not theUnit then return false end
|
||||
local theGroup = theUnit:getGroup()
|
||||
local theCat = theGroup:getCategory() -- 0 == aircraft 1 = plance
|
||||
local lateGroupName = theGroup:getName()
|
||||
local lateType = theUnit:getTypeName()
|
||||
local allTypes = theZone.menuTypes -- {}
|
||||
for idx, aType in pairs(allTypes) do
|
||||
local lowerType = string.lower(aType)
|
||||
if dcsCommon.stringStartsWith(lowerType, "helo") or dcsCommon.stringStartsWith(lowerType, "heli") or
|
||||
aType == "helicopter" then
|
||||
if theCat == 1 then return true end
|
||||
elseif lowerType == "plane" or lowerType == "planes" then
|
||||
if theCat == 0 then return true end
|
||||
else
|
||||
if aType == lateType then return true end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function radioMenu.filterPlayerIDForType(theZone)
|
||||
-- note: we currently ignore coalition
|
||||
local theIDs = {}
|
||||
local allTypes = {}
|
||||
if dcsCommon.containsString(theZone.menuTypes, ",") then
|
||||
allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
|
||||
else
|
||||
table.insert(allTypes, theZone.menuTypes)
|
||||
end
|
||||
local allTypes = theZone.menuTypes -- {}
|
||||
|
||||
-- now iterate all types, and include any player that matches
|
||||
-- note that players may match twice, so we use a dict
|
||||
@ -116,16 +127,47 @@ function radioMenu.filterPlayerIDForType(theZone)
|
||||
return theIDs
|
||||
end
|
||||
|
||||
function radioMenu.lateFilterPlayerForGroup(theUnit, theZone)
|
||||
-- returns true if theUnit matches zone's group criterium
|
||||
-- false otherwise. NO COA CHECK
|
||||
if not theUnit then return false end
|
||||
local theGroup = theUnit:getGroup()
|
||||
local lateGroupName = theGroup:getName()
|
||||
for idx, gName in pairs(theZone.menuGroup) do
|
||||
if dcsCommon.stringEndsWith(gName, "*") then
|
||||
-- we must check all group names if they start with the
|
||||
-- the same root. WARNING: CASE-SENSITIVE!!!!
|
||||
gName = dcsCommon.removeEnding(gName, "*")
|
||||
if dcsCommon.stringStartsWith(lateGroupName, gName) then
|
||||
-- group match, install menu
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: WILDCARD Player Group <" .. gName .. "*> matched with <" .. mxName .. ">: gID = <" .. gID .. ">", 30)
|
||||
end
|
||||
return true
|
||||
end
|
||||
else
|
||||
if lateGroupName == gName then
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> found: <" .. gID .. ">", 30)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function radioMenu.lateFilterCoaForUnit(theUnit, theZone)
|
||||
if theZone.coalition == 0 then return true end
|
||||
if theZone.coalition == theUnit:getCoalition() then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function radioMenu.filterPlayerIDForGroup(theZone)
|
||||
-- create an iterable list of groups, separated by commas
|
||||
-- note that we could introduce wildcards for groups later
|
||||
local theIDs = {}
|
||||
local allGroups = {}
|
||||
if dcsCommon.containsString(theZone.menuGroup, ",") then
|
||||
allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
|
||||
else
|
||||
table.insert(allGroups, theZone.menuGroup)
|
||||
end
|
||||
local allGroups = theZone.menuGroup
|
||||
|
||||
for idx, gName in pairs(allGroups) do
|
||||
-- if gName ends in wildcard "*" we process differently
|
||||
@ -162,6 +204,45 @@ function radioMenu.filterPlayerIDForGroup(theZone)
|
||||
return theIDs
|
||||
end
|
||||
|
||||
function radioMenu.lateInstallMenu(theUnit, theZone)
|
||||
-- we only add the group-individual menus (type/group).
|
||||
-- all higher-level menus have been installed already
|
||||
local theGroup = theUnit:getGroup()
|
||||
local grp = theGroup:getID()
|
||||
local gName = theGroup:getName()
|
||||
radioMenu.lateGroups[grp] = gName
|
||||
if not theZone.rootMenu then theZone.rootMenu = {} end
|
||||
if theZone.attachTo then
|
||||
local mainMenu = theZone.attachTo
|
||||
theZone.mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
|
||||
end
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
-- install menu, drop through to below
|
||||
else
|
||||
--trigger.action.outText("late-skipped menu for <" .. theZone.name .. ">, no group or type dependency", 30)
|
||||
return
|
||||
end
|
||||
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, theZone.mainRoot)
|
||||
theZone.rootMenu[grp] = aRoot
|
||||
theZone.mcdA[grp] = 0
|
||||
theZone.mcdB[grp] = 0
|
||||
theZone.mcdC[grp] = 0
|
||||
theZone.mcdD[grp] = 0
|
||||
if theZone.itemA then
|
||||
theZone.menuA[grp] = missionCommands.addCommandForGroup(grp, theZone.itemA, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "A", grp})
|
||||
end
|
||||
if theZone.itemB then
|
||||
theZone.menuB[grp] = missionCommands.addCommandForGroup(grp, theZone.itemB, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "B", grp})
|
||||
end
|
||||
if theZone.itemC then
|
||||
theZone.menuC[grp] = missionCommands.addCommandForGroup(grp, theZone.itemC, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "C", grp})
|
||||
end
|
||||
if theZone.itemD then
|
||||
theZone.menuD[grp] = missionCommands.addCommandForGroup(grp, theZone.itemD, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "D", grp})
|
||||
end
|
||||
--trigger.action.outText("completed late-add menu for <" .. theZone.name .. "> to <" .. theUnit:getName() .. ">", 30)
|
||||
end
|
||||
|
||||
function radioMenu.installMenu(theZone)
|
||||
local gID = nil
|
||||
if theZone.menuGroup then
|
||||
@ -220,8 +301,8 @@ function radioMenu.installMenu(theZone)
|
||||
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, theZone.mainRoot)
|
||||
end
|
||||
|
||||
if theZone:hasProperty("itemA") then
|
||||
local menuA = theZone:getStringFromZoneProperty("itemA", "<no A submenu>")
|
||||
if theZone.itemA then -- :hasProperty("itemA") then
|
||||
local menuA = theZone.itemA -- theZone:getStringFromZoneProperty("itemA", "<no A submenu>")
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
theZone.menuA = {}
|
||||
for idx, grp in pairs(gID) do
|
||||
@ -234,8 +315,8 @@ function radioMenu.installMenu(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone:hasProperty("itemB") then
|
||||
local menuB = theZone:getStringFromZoneProperty("itemB", "<no B submenu>")
|
||||
if theZone.itemB then --:hasProperty("itemB") then
|
||||
local menuB = theZone.itemB -- :getStringFromZoneProperty("itemB", "<no B submenu>")
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
theZone.menuB = {}
|
||||
for idx, grp in pairs(gID) do
|
||||
@ -248,8 +329,8 @@ function radioMenu.installMenu(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone:hasProperty("itemC") then
|
||||
local menuC = theZone:getStringFromZoneProperty("itemC", "<no C submenu>")
|
||||
if theZone.itemC then --:hasProperty("itemC") then
|
||||
local menuC = theZone.itemC -- :getStringFromZoneProperty("itemC", "<no C submenu>")
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
theZone.menuC = {}
|
||||
for idx, grp in pairs(gID) do
|
||||
@ -262,8 +343,8 @@ function radioMenu.installMenu(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone:hasProperty("itemD") then
|
||||
local menuD = theZone:getStringFromZoneProperty("itemD", "<no D submenu>")
|
||||
if theZone.itemD then -- :hasProperty("itemD") then
|
||||
local menuD = theZone.itemD -- :getStringFromZoneProperty("itemD", "<no D submenu>")
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
theZone.menuD = {}
|
||||
for idx, grp in pairs(gID) do
|
||||
@ -292,6 +373,20 @@ function radioMenu.createRadioMenuWithZone(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
-- read items and their stuff
|
||||
if theZone:hasProperty("itemA") then
|
||||
theZone.itemA = theZone:getStringFromZoneProperty("itemA")
|
||||
end
|
||||
if theZone:hasProperty("itemB") then
|
||||
theZone.itemB = theZone:getStringFromZoneProperty("itemB")
|
||||
end
|
||||
if theZone:hasProperty("itemC") then
|
||||
theZone.itemC = theZone:getStringFromZoneProperty("itemC")
|
||||
end
|
||||
if theZone:hasProperty("itemD") then
|
||||
theZone.itemD = theZone:getStringFromZoneProperty("itemD")
|
||||
end
|
||||
|
||||
theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0)
|
||||
-- groups / types
|
||||
if theZone:hasProperty("group") then
|
||||
@ -306,6 +401,27 @@ function radioMenu.createRadioMenuWithZone(theZone)
|
||||
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
|
||||
end
|
||||
|
||||
-- now process menugroups and create sets to improve later speed
|
||||
if theZone.menuGroup then
|
||||
if dcsCommon.containsString(theZone.menuGroup, ",") then
|
||||
local allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
|
||||
allGroups = dcsCommon.trimArray(allGroups)
|
||||
theZone.menuGroup = allGroups
|
||||
else
|
||||
theZone.menuGroup = {theZone.menuGroup} --table.insert(allGroups, theZone.menuGroup)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone.menuTypes then
|
||||
if dcsCommon.containsString(theZone.menuTypes, ",") then
|
||||
local allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
|
||||
allTypes = dcsCommon.trimArray(allTypes)
|
||||
theZone.menuTypes = allTypes
|
||||
else
|
||||
theZone.menuTypes = {theZone.menuTypes}
|
||||
end
|
||||
end
|
||||
|
||||
theZone.menuVisible = theZone:getBoolFromZoneProperty("menuVisible", true)
|
||||
|
||||
-- install menu if not hidden
|
||||
@ -392,13 +508,25 @@ end
|
||||
function radioMenu.getMainMenuFor(mainMenu, theZone, idx)
|
||||
if not idx then idx = 0 end
|
||||
if not mainMenu.rootMenu[idx] then
|
||||
-- trigger.action.outText("main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">: forcing idx to 0", 30)
|
||||
return mainMenu.rootMenu[0]
|
||||
end
|
||||
-- trigger.action.outText("good main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">", 30)
|
||||
return mainMenu.rootMenu[idx]
|
||||
end
|
||||
|
||||
function radioMenu.lateInstallMainMenu(theUnit, theZone)
|
||||
local theGroup = theUnit:getGroup()
|
||||
local grp = theGroup:getID()
|
||||
local mainRoot = nil
|
||||
if not theZone.rootMenu then theZone.rootMenu = {} end
|
||||
if theZone.attachTo then
|
||||
local mainMenu = theZone.attachTo
|
||||
mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
|
||||
end
|
||||
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, mainRoot)
|
||||
theZone.rootMenu[grp] = aRoot
|
||||
--trigger.action.outText("menu: late-attached main menu <" .. theZone.name .. "> for unit <" .. theUnit:getName() .. ">", 30)
|
||||
end
|
||||
|
||||
function radioMenu.installMainMenu(theZone)
|
||||
local gID = nil -- set of all groups this menu applies to
|
||||
if theZone.menuGroup then
|
||||
@ -490,6 +618,27 @@ function radioMenu.createRadioMainMenuWithZone(theZone)
|
||||
elseif theZone:hasProperty("types") then
|
||||
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
|
||||
end
|
||||
|
||||
-- now process menugroups and create sets to improve later speed
|
||||
if theZone.menuGroup then
|
||||
if dcsCommon.containsString(theZone.menuGroup, ",") then
|
||||
local allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
|
||||
allGroups = dcsCommon.trimArray(allGroups)
|
||||
theZone.menuGroup = allGroups
|
||||
else
|
||||
theZone.menuGroup = {theZone.menuGroup} --table.insert(allGroups, theZone.menuGroup)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone.menuTypes then
|
||||
if dcsCommon.containsString(theZone.menuTypes, ",") then
|
||||
local allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
|
||||
allTypes = dcsCommon.trimArray(allTypes)
|
||||
theZone.menuTypes = allTypes
|
||||
else
|
||||
theZone.menuTypes = {theZone.menuTypes}
|
||||
end
|
||||
end
|
||||
|
||||
-- always install this one
|
||||
radioMenu.installMainMenu(theZone)
|
||||
@ -523,6 +672,8 @@ function radioMenu.radioOutMsg(ack, gid, theZone)
|
||||
local theMsg = ack
|
||||
if (gid > 0) and cfxMX then
|
||||
local gName = cfxMX.groupNamesByID[gid]
|
||||
if not gName then gName = radioMenu.lateGroups[gid] end
|
||||
if not gName then gName = "?*?*?" end
|
||||
theMsg = theMsg:gsub("<group>", gName)
|
||||
end
|
||||
|
||||
@ -566,9 +717,9 @@ function radioMenu.setCDByGID(cd, theZone, gID, newVal)
|
||||
end
|
||||
|
||||
function radioMenu.doMenuX(args)
|
||||
theZone = args[1]
|
||||
theItemIndex = args[2] -- A, B , C .. ?
|
||||
theGroup = args[3] -- can be nil or groupID
|
||||
local theZone = args[1]
|
||||
local theItemIndex = args[2] -- A, B , C .. ?
|
||||
local theGroup = args[3] -- can be nil or groupID
|
||||
if not theGroup then theGroup = 0 end
|
||||
|
||||
local cd = radioMenu.cdByGID(theZone.mcdA, theZone, theGroup) --theZone.mcdA
|
||||
@ -688,6 +839,74 @@ function radioMenu.update()
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- onEvent - late dynamic spawns
|
||||
--
|
||||
function radioMenu.lateMenuAddForUnit(theUnit)
|
||||
-- iterate all menu zones and determine if this menu is to
|
||||
-- install
|
||||
local uName = theUnit:getName()
|
||||
for zName, theZone in pairs(radioMenu.mainMenus) do
|
||||
-- determine if this applies to us, and if so, install
|
||||
-- for unit. only do this if menuGroups or menuTypes present
|
||||
-- all others are set on coa level or higher
|
||||
--trigger.action.outText("late-proccing MAIN menu <" .. zName ..">", 30)
|
||||
if theZone.menuGroup then
|
||||
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
|
||||
radioMenu.lateFilterPlayerForGroup(theUnit, theZone) then
|
||||
radioMenu.lateInstallMainMenu(theUnit, theZone)
|
||||
else
|
||||
--trigger.action.outText("menu: skipped GROUP main menu <" .. zName .. "> add for <" .. uName .. ">", 30)
|
||||
end
|
||||
elseif theZone.menuTypes then
|
||||
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
|
||||
radioMenu.lateFilterPlayerForType(theUnit, theZone) then
|
||||
radioMenu.lateInstallMainMenu(theUnit, theZone)
|
||||
else
|
||||
--trigger.action.outText("menu: skipped TYPE main menu <" .. zName .. "> add for <" .. uName .. ">", 30)
|
||||
end
|
||||
else
|
||||
-- nothing to do, was attached for coalition or higher
|
||||
--trigger.action.outText("menu: did not late-install main menu <" .. zName .. ">, no group or type restrictions", 30)
|
||||
end
|
||||
end
|
||||
|
||||
for idx, theZone in pairs(radioMenu.menus) do
|
||||
-- again, does this apply to us?
|
||||
--trigger.action.outText("late-proccing menu <" .. theZone.name ..">", 30)
|
||||
if theZone.menuGroup then
|
||||
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
|
||||
radioMenu.lateFilterPlayerForGroup(theUnit, theZone) then
|
||||
radioMenu.lateInstallMenu(theUnit, theZone)
|
||||
else
|
||||
--trigger.action.outText("menu: skipped GROUP main menu <" .. theZone.name .. "> add for <" .. uName .. ">", 30)
|
||||
end
|
||||
elseif theZone.menuTypes then
|
||||
if radioMenu.lateFilterCoaForUnit(theUnit, theZone) and
|
||||
radioMenu.lateFilterPlayerForType(theUnit, theZone) then
|
||||
radioMenu.lateInstallMenu(theUnit, theZone)
|
||||
else
|
||||
--trigger.action.outText("menu: skipped TYPE main menu <" .. theZone.name .. "> add for <" .. uName .. ">", 30)
|
||||
end
|
||||
else
|
||||
-- nothing to do, was attached for coalition or higher
|
||||
--trigger.action.outText("menu: did not late-install STD menu <" .. theZone.name .. ">, no group or type restrictions", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function radioMenu:onEvent(event)
|
||||
if not event then return end
|
||||
if not event.initiator then return end
|
||||
local theUnit = event.initiator
|
||||
if event.id == 15 then
|
||||
if not cfxMX.isDynamicPlayer(theUnit) then return end
|
||||
-- we have a dynamic unit spawn
|
||||
--trigger.action.outText("menu: detected dynamic spawn <" .. theUnit:getName() .. ">", 30)
|
||||
radioMenu.lateMenuAddForUnit(theUnit)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Config & Start
|
||||
@ -756,6 +975,9 @@ function radioMenu.start()
|
||||
radioMenu.addRadioMenu(aZone) -- add to list
|
||||
end
|
||||
|
||||
-- install late spawn detector
|
||||
world.addEventHandler(radioMenu)
|
||||
|
||||
-- start update
|
||||
radioMenu.update()
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
reaper = {}
|
||||
reaper.version = "1.1.0"
|
||||
reaper.version = "1.2.0"
|
||||
reaper.requiredLibs = {
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
@ -22,6 +22,7 @@ VERSION HISTORY
|
||||
- added FAC task
|
||||
- split task generation from wp generation
|
||||
- updated reaper naming, uniqueNames attribute (undocumented)
|
||||
1.2.0 - support twn when present
|
||||
|
||||
--]]--
|
||||
|
||||
@ -290,12 +291,21 @@ function reaper.setTarget(theZone, theTarget, cycled)
|
||||
local lp = theTarget:getPoint()
|
||||
local lat, lon, alt = coord.LOtoLL(lp)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
local twnLoc = ""
|
||||
if twn and towns then
|
||||
local name, data, dist = twn.closestTownTo(lp)
|
||||
local mdist= dist * 0.539957
|
||||
dist = math.floor(dist/100) / 10
|
||||
mdist = math.floor(mdist/100) / 10
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
|
||||
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. name .. ")"
|
||||
end
|
||||
|
||||
local theSpot = Spot.createLaser(theZone.theUav, {0, 2, 0}, lp, theZone.code)
|
||||
if theZone.doSmoke then
|
||||
trigger.action.smoke(lp , theZone.smokeColor )
|
||||
end
|
||||
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> is tracking a <" .. theTarget:getTypeName() .. "> at " .. lat .. " " .. lon .. ", code " .. theZone.code, 30)
|
||||
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> is tracking a <" .. theTarget:getTypeName() .. "> at " .. lat .. " " .. lon .. twnLoc ..", code " .. theZone.code, 30)
|
||||
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
|
||||
theZone.theTarget = theTarget
|
||||
if theZone.theSpot then
|
||||
@ -599,14 +609,23 @@ function reaper.doDroneStatus(args)
|
||||
local lat, lon, alt = coord.LOtoLL(lp)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
local ut = theTarget:getTypeName()
|
||||
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
|
||||
local twnLoc = ""
|
||||
if twn and towns then
|
||||
local tname, data, dist = twn.closestTownTo(lp)
|
||||
local mdist= dist * 0.539957
|
||||
dist = math.floor(dist/100) / 10
|
||||
mdist = math.floor(mdist/100) / 10
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
|
||||
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. tname .. ") "
|
||||
end
|
||||
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. twnLoc .. " code " .. theZone.code
|
||||
else
|
||||
msg = msg .. "<signal failure, please try later>"
|
||||
end
|
||||
done[name] = true
|
||||
end
|
||||
else
|
||||
msg = msg .. "\n(No drones are tracking a target)\n"
|
||||
msg = msg .. "\n(No drones are tracking targets)\n"
|
||||
end
|
||||
|
||||
-- collect loitering drones
|
||||
@ -661,7 +680,16 @@ function reaper.doSingleDroneStatus(theZone)
|
||||
local lat, lon, alt = coord.LOtoLL(lp)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
local ut = theTarget:getTypeName()
|
||||
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
|
||||
local twnLoc = ""
|
||||
if twn and towns then
|
||||
local tname, data, dist = twn.closestTownTo(lp)
|
||||
local mdist= dist * 0.539957
|
||||
dist = math.floor(dist/100) / 10
|
||||
mdist = math.floor(mdist/100) / 10
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(lp, data.p)
|
||||
twnLoc = " (" ..dist .. "km/" .. mdist .."nm " .. bear .. " of " .. tname .. ") "
|
||||
end
|
||||
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. twnLoc .. " code " .. theZone.code
|
||||
|
||||
-- now add full group intelligence
|
||||
local collector = {}
|
||||
@ -886,7 +914,6 @@ function reaper.start()
|
||||
timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1)
|
||||
|
||||
-- schedule scan and track loops
|
||||
-- timer.scheduleFunction(reaper.scan, {}, timer.getTime() + 1)
|
||||
timer.scheduleFunction(reaper.scanALT, {}, timer.getTime() + 1)
|
||||
timer.scheduleFunction(reaper.track, {}, timer.getTime() + 1)
|
||||
trigger.action.outText("reaper v " .. reaper.version .. " running.", 30)
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
cfxReconGUI = {}
|
||||
cfxReconGUI.version = "1.0.0"
|
||||
cfxReconGUI.version = "2.0.0"
|
||||
--[[-- VERSION HISTORY
|
||||
- 1.0.0 - initial version
|
||||
|
||||
- 2.0.0 - removed dependence on cfxPlayer
|
||||
- compatible with dynamically spawning players
|
||||
- cleanup
|
||||
--]]--
|
||||
-- find & command cfxGroundTroops-based jtacs
|
||||
-- UI installed via OTHER for all groups with players
|
||||
-- module based on xxxGrpUI
|
||||
|
||||
|
||||
cfxReconGUI.groupConfig = {} -- all inited group private config data
|
||||
cfxReconGUI.simpleCommands = true -- if true, f10 other invokes directly
|
||||
|
||||
cfxReconGUI.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
--
|
||||
-- C O N F I G H A N D L I N G
|
||||
-- =============================
|
||||
@ -38,7 +40,6 @@ function cfxReconGUI.createDefaultConfig(theGroup)
|
||||
local groupUnits = theGroup:getUnits()
|
||||
conf.unit = groupUnits[1] -- WARNING: ASSUMES ONE-UNIT GROUPS
|
||||
cfxReconGUI.resetConfig(conf)
|
||||
|
||||
conf.mainMenu = nil; -- this is where we store the main menu if we branch
|
||||
conf.myCommands = nil; -- this is where we store the commands if we branch
|
||||
|
||||
@ -80,12 +81,10 @@ function cfxReconGUI.getConfigForUnit(theUnit)
|
||||
return conf
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- M E N U H A N D L I N G
|
||||
-- =========================
|
||||
--
|
||||
--
|
||||
|
||||
function cfxReconGUI.clearCommsSubmenus(conf)
|
||||
if conf.myCommands then
|
||||
for i=1, #conf.myCommands do
|
||||
@ -96,16 +95,14 @@ end
|
||||
end
|
||||
|
||||
function cfxReconGUI.removeCommsFromConfig(conf)
|
||||
cfxReconGUI.clearCommsSubmenus(conf)
|
||||
|
||||
cfxReconGUI.clearCommsSubmenus(conf)
|
||||
if conf.myMainMenu then
|
||||
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
|
||||
conf.myMainMenu = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- this only works in single-unit groups. may want to check if group
|
||||
-- has disappeared
|
||||
-- this only works in single-unit groups
|
||||
function cfxReconGUI.removeCommsForUnit(theUnit)
|
||||
if not theUnit then return end
|
||||
if not theUnit:isExist() then return end
|
||||
@ -121,9 +118,6 @@ function cfxReconGUI.removeCommsForGroup(theGroup)
|
||||
cfxReconGUI.removeCommsFromConfig(conf)
|
||||
end
|
||||
|
||||
--
|
||||
-- set main root in F10 Other. All sub menus click into this
|
||||
--
|
||||
function cfxReconGUI.isEligibleForMenu(theGroup)
|
||||
return true
|
||||
end
|
||||
@ -144,7 +138,7 @@ end
|
||||
|
||||
function cfxReconGUI.setCommsMenu(theGroup)
|
||||
-- depending on own load state, we set the command structure
|
||||
-- it begins at 10-other, and has 'jtac' as main menu with submenus
|
||||
-- it begins at F10-Other, and has 'Recon' as main menu with submenus
|
||||
-- as required
|
||||
if not theGroup then return end
|
||||
if not theGroup:isExist() then return end
|
||||
@ -154,8 +148,7 @@ function cfxReconGUI.setCommsMenu(theGroup)
|
||||
if not cfxReconGUI.isEligibleForMenu(theGroup) then return end
|
||||
|
||||
local conf = cfxReconGUI.getConfigForGroup(theGroup)
|
||||
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash
|
||||
-- trigger.action.outText("+++ setting group <".. conf.theGroup:getName() .. "> jtac command", 30)
|
||||
conf.id = theGroup:getID(); -- we ALWAYSdo this
|
||||
|
||||
if cfxReconGUI.simpleCommands then
|
||||
-- we install directly in F-10 other
|
||||
@ -190,26 +183,17 @@ function cfxReconGUI.setCommsMenu(theGroup)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- ok, first, if we don't have an F-10 menu, create one
|
||||
-- if we don't have an F-10 menu, create one
|
||||
if not (conf.myMainMenu) then
|
||||
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'Recon')
|
||||
end
|
||||
|
||||
-- clear out existing commands
|
||||
cfxReconGUI.clearCommsSubmenus(conf)
|
||||
|
||||
-- now we have a menu without submenus.
|
||||
-- add our own submenus
|
||||
cfxReconGUI.addSubMenus(conf)
|
||||
|
||||
cfxReconGUI.addSubMenus(conf)
|
||||
end
|
||||
|
||||
function cfxReconGUI.addSubMenus(conf)
|
||||
-- add menu items to choose from after
|
||||
-- user clickedf on MAIN MENU. In this implementation
|
||||
-- they all result invoked methods
|
||||
|
||||
local commandTxt = "Recon"
|
||||
local unitName = "bogus"
|
||||
if conf.unit and conf.unit:getName()then
|
||||
@ -219,32 +203,10 @@ function cfxReconGUI.addSubMenus(conf)
|
||||
commandTxt = commandTxt .. "***"
|
||||
end
|
||||
|
||||
local theCommand = missionCommands.addCommandForGroup(
|
||||
conf.id,
|
||||
commandTxt,
|
||||
conf.myMainMenu,
|
||||
cfxReconGUI.redirectCommandX,
|
||||
{conf, "recon", unitName}
|
||||
)
|
||||
local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, conf.myMainMenu, cfxReconGUI.redirectCommandX, {conf, "recon", unitName})
|
||||
table.insert(conf.myCommands, theCommand)
|
||||
--[[--
|
||||
commandTxt = "This is another important command"
|
||||
theCommand = missionCommands.addCommandForGroup(
|
||||
conf.id,
|
||||
commandTxt,
|
||||
conf.myMainMenu,
|
||||
cfxReconGUI.redirectCommandX,
|
||||
{conf, "Sub2"}
|
||||
)
|
||||
table.insert(conf.myCommands, theCommand)
|
||||
--]]--
|
||||
end
|
||||
|
||||
--
|
||||
-- each menu item has a redirect and timed invoke to divorce from the
|
||||
-- no-debug zone in the menu invocation. Delay is .1 seconds
|
||||
--
|
||||
|
||||
function cfxReconGUI.redirectCommandX(args)
|
||||
timer.scheduleFunction(cfxReconGUI.doCommandX, args, timer.getTime() + 0.1)
|
||||
end
|
||||
@ -260,10 +222,8 @@ function cfxReconGUI.doCommandX(args)
|
||||
trigger.action.outText("+++ reconUI: doCommand: BOGUS unitName!", 30)
|
||||
end
|
||||
|
||||
local theGroup = conf.theGroup
|
||||
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
|
||||
|
||||
-- whenever we get here, we toggle the recon mode
|
||||
local theGroup = conf.theGroup
|
||||
-- when we get here, we toggle the recon mode
|
||||
local theUnit = conf.unit
|
||||
local message = "Scout ".. unitName .. " has stopped reporting."
|
||||
local theSide = conf.coalition
|
||||
@ -285,94 +245,50 @@ function cfxReconGUI.doCommandX(args)
|
||||
end
|
||||
end
|
||||
trigger.action.outTextForCoalition(theSide, message, 30)
|
||||
|
||||
-- reset comms
|
||||
cfxReconGUI.removeCommsForGroup(theGroup)
|
||||
cfxReconGUI.setCommsMenu(theGroup)
|
||||
end
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- G R O U P M A N A G E M E N T
|
||||
--
|
||||
-- Group Management is required to make sure all groups
|
||||
-- receive a comms menu and that they receive a clean-up
|
||||
-- when required
|
||||
--
|
||||
-- Callbacks are provided by cfxPlayer module to which we
|
||||
-- subscribe during init
|
||||
--
|
||||
function cfxReconGUI.playerChangeEvent(evType, description, player, data)
|
||||
--trigger.action.outText("+++ groupUI: received <".. evType .. "> Event", 30)
|
||||
if evType == "newGroup" then
|
||||
-- initialized attributes are in data as follows
|
||||
-- .group - new group
|
||||
-- .name - new group's name
|
||||
-- .primeUnit - the unit that trigggered new group appearing
|
||||
-- .primeUnitName - name of prime unit
|
||||
-- .id group ID
|
||||
--theUnit = data.primeUnit
|
||||
-- ensure group data exists and is updated
|
||||
local conf = cfxReconGUI.getConfigForGroup(data.group)
|
||||
conf.unit = data.primeUnit
|
||||
conf.unitName = conf.unit:getName() -- will break if no exist
|
||||
|
||||
cfxReconGUI.setCommsMenu(data.group)
|
||||
-- trigger.action.outText("+++ groupUI: added " .. theUnit:getName() .. " to comms menu", 30)
|
||||
return
|
||||
function cfxReconGUI:onEvent(theEvent)
|
||||
if not theEvent then return end
|
||||
if not theEvent.initiator then return end
|
||||
local theUnit = theEvent.initiator
|
||||
if not Unit.isExist(theUnit) then return end
|
||||
if not theUnit.getName then return end
|
||||
if not theUnit.getGroup then return end
|
||||
if not theUnit.getPlayerName then return end
|
||||
if not theUnit:getPlayerName() then return end
|
||||
local theGroup = theUnit:getGroup()
|
||||
if theEvent.id == 15 then
|
||||
-- BIRTH EVENT PLAYER
|
||||
local conf = cfxReconGUI.getConfigForGroup(theGroup)
|
||||
conf.unit = theUnit --data.primeUnit
|
||||
conf.unitName = theUnit:getName()
|
||||
cfxReconGUI.setCommsMenu(theGroup)
|
||||
end
|
||||
|
||||
if evType == "removeGroup" then
|
||||
-- data is the player record that no longer exists. it consists of
|
||||
-- .name
|
||||
-- we must remove the comms menu for this group else we try to add another one to this group later
|
||||
local conf = cfxReconGUI.getConfigByGroupName(data.name)
|
||||
|
||||
if conf then
|
||||
cfxReconGUI.removeCommsFromConfig(conf) -- remove menus
|
||||
cfxReconGUI.resetConfig(conf) -- re-init this group for when it re-appears
|
||||
else
|
||||
trigger.action.outText("+++ reconUI: can't retrieve group <" .. data.name .. "> config: not found!", 30)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if evType == "leave" then
|
||||
-- player unit left. we don't care since we only work on group level
|
||||
-- if they were the only, this is followed up by group disappeared
|
||||
|
||||
end
|
||||
|
||||
if evType == "unit" then
|
||||
-- player changed units. almost never in MP, but possible in solo
|
||||
-- because of 1 seconds timing loop
|
||||
-- will result in a new group appearing and a group disappearing, so we are good
|
||||
-- may need some logic to clean up old configs and/or menu items
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- Start
|
||||
--
|
||||
function cfxReconGUI.start()
|
||||
|
||||
-- iterate existing groups so we have a start situation
|
||||
-- now iterate through all player groups and install the Assault Troop Menu
|
||||
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
|
||||
-- contains per group player record. Does not resolve on unit level!
|
||||
for gname, pgroup in pairs(allPlayerGroups) do
|
||||
local theUnit = pgroup.primeUnit -- get any unit of that group
|
||||
cfxReconGUI.setCommsMenuForUnit(theUnit) -- set up
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck("cfx Recon Mode",
|
||||
cfxReconMode.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
-- now install the new group notifier to install Assault Troops menu
|
||||
|
||||
cfxPlayer.addMonitor(cfxReconGUI.playerChangeEvent)
|
||||
-- iterate existing groups so we have a start situation
|
||||
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
|
||||
for idx, theUnit in pairs(allPlayerUnits) do
|
||||
cfxReconGUI.setCommsMenuForUnit(theUnit)
|
||||
end
|
||||
|
||||
world.addEventHandler(cfxReconGUI)
|
||||
|
||||
trigger.action.outText("cf/x cfxReconGUI v" .. cfxReconGUI.version .. " started", 30)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxReconMode = {}
|
||||
cfxReconMode.version = "2.2.2"
|
||||
cfxReconMode.version = "2.3.0"
|
||||
cfxReconMode.verbose = false -- set to true for debug info
|
||||
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
|
||||
|
||||
@ -60,7 +60,8 @@ VERSION HISTORY
|
||||
2.2.1 - fixed "cfxReconSMode" typo
|
||||
2.2.2 - added groupNames attribute
|
||||
- clean-up
|
||||
|
||||
2.3.0 - support for towns/twn when present
|
||||
|
||||
--]]--
|
||||
|
||||
cfxReconMode.detectionMinRange = 3000 -- meters at ground level
|
||||
@ -394,6 +395,18 @@ function cfxReconMode.getLocation(theGroup)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele ..units
|
||||
end
|
||||
|
||||
if twn and towns then
|
||||
units = "km"
|
||||
local village, data, dist = twn.closestTownTo(currPoint)
|
||||
if cfxReconMode.imperialUnits then
|
||||
dist = dist * 0.539957 -- nm conversion
|
||||
units = "nm"
|
||||
end
|
||||
dist = math.floor(dist/100) / 10
|
||||
local bear = dcsCommon.compassPositionOfARelativeToB(currPoint, data.p)
|
||||
msg = msg .. ", " .. dist .. units .. " " .. bear .. " of " .. village
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
scribe = {}
|
||||
scribe.version = "2.0.0"
|
||||
scribe.version = "2.0.2"
|
||||
scribe.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -13,10 +13,15 @@ VERSION HISTORY
|
||||
1.0.1 postponed land, postponed takeoff, unit_lost
|
||||
1.1.0 supports persistence's SHARED ability to share data across missions
|
||||
2.0.0 support for main menu
|
||||
2.0.1 Hardening for DCS Jul 11 patch issues
|
||||
2.0.2 Secondary landing events correction
|
||||
support for DCS dynamic player spawns
|
||||
|
||||
--]]--
|
||||
scribe.verbose = true
|
||||
scribe.db = {} -- indexed by player name
|
||||
scribe.playerUnits = {} -- indexed by unit name. for crash detection
|
||||
scribe.dynamicPlayers = {}
|
||||
|
||||
--[[--
|
||||
unitEntry:
|
||||
@ -157,12 +162,22 @@ end
|
||||
-- Event handling
|
||||
--
|
||||
function scribe.playerBirthedIn(playerName, theUnit)
|
||||
-- access db
|
||||
local theEntry = scribe.getPlayerNamed(playerName) -- can be new
|
||||
local myType = theUnit:getTypeName()
|
||||
local uName = theUnit:getName()
|
||||
local theGroup = theUnit:getGroup()
|
||||
local gID = theGroup:getID()
|
||||
-- install menu if dynamic plane and not defined already
|
||||
if cfxMX.isDynamicPlayer(theUnit) then
|
||||
local gName = theGroup:getName()
|
||||
if not scribe.dynamicPlayers[gName] then
|
||||
scribe.installDynamicPlayerMenu(theUnit)
|
||||
scribe.dynamicPlayers[gName] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- access db
|
||||
local theEntry = scribe.getPlayerNamed(playerName) -- can be new
|
||||
|
||||
-- check if this player is still active
|
||||
if theEntry.isActive then
|
||||
-- do something to remedy this
|
||||
@ -281,15 +296,15 @@ function scribe.playerLanded(playerName)
|
||||
-- see if last landing is at least xx seconds old
|
||||
local now = timer.getTime()
|
||||
delta = now - uEntry.lastLanding
|
||||
if delta > scribe.landingCD or delta < 0 then
|
||||
if delta > scribe.landingCD then -- or delta < 0 then
|
||||
uEntry.landings = uEntry.landings + 1
|
||||
-- trigger.action.outText("+++scrb: added landing for " .. playerName .. ", delta is <" .. delta .. ">.", 30)
|
||||
else
|
||||
if scribe.verbose then
|
||||
trigger.action.outText("+++scb: landing ignored: cooldown active", 30)
|
||||
end
|
||||
end
|
||||
uEntry.lastLanding = now
|
||||
|
||||
end
|
||||
|
||||
function scribe.playerDeparted(playerName)
|
||||
@ -342,6 +357,7 @@ function scribe:onEvent(theEvent)
|
||||
if not theEvent.initiator then return end
|
||||
local theUnit = theEvent.initiator
|
||||
if not theUnit then return end
|
||||
if not theUnit.getName then return end -- DCS bug hardening
|
||||
local uName = theUnit:getName()
|
||||
if scribe.playerUnits[uName] and scribe.verbose then
|
||||
trigger.action.outText("+++scb: event <" .. theEvent.id .. " = " .. dcsCommon.event2text(theEvent.id) .. ">, concerns player unit named <" .. uName .. ">.", 30)
|
||||
@ -362,10 +378,13 @@ function scribe:onEvent(theEvent)
|
||||
return
|
||||
end
|
||||
-- when we get here we have a player event
|
||||
|
||||
-- trigger.action.outText("+++scrb: player event <" .. theEvent.id .. ">", 30)
|
||||
-- players can only ever activate by birth event
|
||||
if theEvent.id == 15 then -- birth
|
||||
scribe.playerBirthedIn(playerName, theUnit)
|
||||
if theEvent.id == 15
|
||||
or theEvent == 20
|
||||
then -- birth / enter unit
|
||||
-- trigger.action.outText("+++scrb: player <" .. playerName .. "> entered unit.", 30)
|
||||
scribe.playerBirthedIn(playerName, theUnit) -- reset timer for landings / take-off
|
||||
scribe.playerUnits[uName] = playerName -- for crash helo detection
|
||||
end
|
||||
|
||||
@ -384,12 +403,12 @@ function scribe:onEvent(theEvent)
|
||||
end
|
||||
|
||||
if theEvent.id == 4 or -- landed
|
||||
theEvent.id == 56 then
|
||||
theEvent.id == 55 then -- corrected to 55
|
||||
scribe.playerLanded(playerName)
|
||||
end
|
||||
|
||||
if theEvent.id == 3 or -- take-off
|
||||
theEvent.id == 55 then -- postponed take-off
|
||||
theEvent.id == 54 then -- postponed take-off, corrected to 54
|
||||
scribe.playerDeparted(playerName)
|
||||
-- trigger.action.outText("departure detected", 30)
|
||||
end
|
||||
@ -484,6 +503,31 @@ end
|
||||
--
|
||||
-- start
|
||||
--
|
||||
function scribe.installDynamicPlayerMenu(theUnit)
|
||||
local mainMenu = nil
|
||||
if scribe.mainMenu then
|
||||
mainMenu = radioMenu.getMainMenuFor(scribe.mainMenu) -- nilling both next params will return menus[0]
|
||||
end
|
||||
local unitInfo = {}
|
||||
local theGroup = theUnit:getGroup()
|
||||
local coa = theGroup:getCoalition()
|
||||
local theType = theUnit:getTypeName()
|
||||
local gName = theGroup:getName()
|
||||
local uName = theUnit:getName()
|
||||
if scribe.verbose then
|
||||
trigger.action.outText("DYNAMIC unit <" .. uName .. ">: type <" .. theType .. "> coa <" .. coa .. ">, group <" .. gName .. ">", 30)
|
||||
end
|
||||
unitInfo.uName = uName -- needed for reverse-lookup
|
||||
unitInfo.gName = gName -- also needed for reverse lookup
|
||||
unitInfo.coa = coa
|
||||
unitInfo.gID = theGroup:getID()
|
||||
unitInfo.uID = theUnit:getID()
|
||||
unitInfo.theType = theType
|
||||
-- unitInfo.cat = cfxMX.groupTypeByName[gName]
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu, mainMenu)
|
||||
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
|
||||
end
|
||||
|
||||
function scribe.startPlayerGUI()
|
||||
-- scan all mx players
|
||||
-- note: currently assumes single-player groups
|
||||
@ -514,7 +558,7 @@ function scribe.startPlayerGUI()
|
||||
unitInfo.gID = gData.groupId
|
||||
unitInfo.uID = uData.unitId
|
||||
unitInfo.theType = theType
|
||||
unitInfo.cat = cfxMX.groupTypeByName[gName]
|
||||
-- unitInfo.cat = cfxMX.groupTypeByName[gName]
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu, mainMenu)
|
||||
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxSpawnZones = {}
|
||||
cfxSpawnZones.version = "2.0.3"
|
||||
cfxSpawnZones.version = "2.1.0"
|
||||
cfxSpawnZones.requiredLibs = {
|
||||
"dcsCommon", -- common is of course needed for everything
|
||||
-- pretty stupid to check for this since we
|
||||
@ -29,6 +29,8 @@ cfxSpawnZones.spawnedGroups = {}
|
||||
2.0.1 - fix in verifySpawnOwnership() when not master zone found
|
||||
2.0.2 - new "moveFormation" attribute
|
||||
2.0.3 - corrected type in spawnUnits? attribute
|
||||
2.1.0 - masterOwner update for dmlZones.
|
||||
since spawners don't extend zones, this is still old-school
|
||||
|
||||
--]]--
|
||||
|
||||
@ -111,7 +113,17 @@ function cfxSpawnZones.createSpawner(inZone)
|
||||
theSpawner.country = inZone:getNumberFromZoneProperty("country", 0)
|
||||
if inZone:hasProperty("masterOwner") then
|
||||
theSpawner.masterZoneName = inZone:getStringFromZoneProperty("masterOwner", "")
|
||||
if theSpawner.masterZoneName == "" then theSpawner.masterZoneName = nil end
|
||||
if theSpawner.masterZoneName == "" then
|
||||
theSpawner.masterZoneName = nil
|
||||
else
|
||||
local masterZone = cfxZones.getZoneByName(theSpawner.masterZoneName)
|
||||
if not masterZone then
|
||||
trigger.action.outText("spawner " .. theSpawner.name .. " DID NOT FIND MASTER ZONE <" .. theSpawner.masterZoneName .. ">", 30)
|
||||
theSpawner.masterZoneName = nil
|
||||
else
|
||||
theSpawner.masterZone = masterZone
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country)
|
||||
@ -237,18 +249,18 @@ function cfxSpawnZones.verifySpawnOwnership(spawner)
|
||||
return true
|
||||
end -- no master owner, all ok
|
||||
local myCoalition = spawner.rawOwner
|
||||
local masterZone = cfxZones.getZoneByName(spawner.masterZoneName)
|
||||
if not masterZone then
|
||||
trigger.action.outText("spawner " .. spawner.name .. " DID NOT FIND MASTER ZONE <" .. spawner.masterZoneName .. ">", 30)
|
||||
return false
|
||||
end
|
||||
|
||||
if not masterZone.owner then
|
||||
-- local masterZone = cfxZones.getZoneByName(spawner.masterZoneName)
|
||||
-- if not masterZone then
|
||||
-- trigger.action.outText("spawner " .. spawner.name .. " DID NOT FIND MASTER ZONE <" .. spawner.masterZoneName .. ">", 30)
|
||||
-- return false
|
||||
-- end
|
||||
local masterZone = spawner.masterZone
|
||||
-- if not masterZone.owner then
|
||||
--trigger.action.outText("spawner " .. spawner.name .. " - masterZone " .. masterZone.name .. " HAS NO OWNER????", 30)
|
||||
return true
|
||||
end
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
if (myCoalition ~= masterZone.owner) then
|
||||
if (myCoalition ~= masterZone:getCoalition()) then
|
||||
-- can't spawn, surrounding area owned by enemy
|
||||
return false
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
stopGap = {}
|
||||
stopGap.version = "1.1.1 STANDALONE"
|
||||
stopGap.version = "1.2.0 STANDALONE"
|
||||
stopGap.verbose = false
|
||||
stopGap.ssbEnabled = true
|
||||
stopGap.ignoreMe = "-sg"
|
||||
@ -8,6 +8,7 @@ stopGap.isMP = false
|
||||
stopGap.running = true
|
||||
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
|
||||
stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-entry issues
|
||||
stopGap.allNeutral = false -- make all stand-ins neutral
|
||||
|
||||
--[[--
|
||||
Written and (c) 2023 by Christian Franz
|
||||
@ -37,6 +38,8 @@ stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-
|
||||
1.0.9 - optimization when turning on stopgap
|
||||
1.1.0 - kickTheDead option
|
||||
1.1.1 - filter "from runway" clients
|
||||
1.2.0 - compatibility with DCS dynamic spawns
|
||||
|
||||
|
||||
--]]--
|
||||
|
||||
@ -115,8 +118,12 @@ function stopGap.staticMXFromUnitMX(theGroup, theUnit)
|
||||
theStatic.heading = theUnit.heading -- may need some attention
|
||||
theStatic.type = theUnit.type
|
||||
theStatic.name = theUnit.name -- will magically be replaced with player unit
|
||||
theStatic.payload = theUnit.payload -- not supported (yet) by DCS
|
||||
theStatic.onboard_num = theUnit.onboard_num -- not supported
|
||||
theStatic.cty = cfxMX.countryByName[theGroup.name]
|
||||
|
||||
if stopGap.allNeutral then
|
||||
theStatic.cty = 82 -- UN Peache keepers, assign to neutral
|
||||
end
|
||||
return theStatic
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
stopGap = {}
|
||||
stopGap.version = "1.1.2"
|
||||
stopGap.version = "1.2.0"
|
||||
stopGap.verbose = false
|
||||
stopGap.ssbEnabled = true
|
||||
stopGap.ignoreMe = "-sg"
|
||||
@ -52,6 +52,9 @@ stopGap.requiredLibs = {
|
||||
1.1.0 - kickTheDead option
|
||||
1.1.1 - filter "from runway" clients
|
||||
1.1.2 - allNeutral (DML only)
|
||||
1.2.0 - DCS dynamic player spawn compatibility
|
||||
stopGaps only works with MX data, so we are good, no changes
|
||||
required
|
||||
|
||||
--]]--
|
||||
|
||||
@ -77,6 +80,8 @@ function stopGap.staticMXFromUnitMX(theGroup, theUnit)
|
||||
theStatic.type = theUnit.type
|
||||
theStatic.name = theUnit.name -- will magically be replaced with player unit
|
||||
theStatic.cty = cfxMX.countryByName[theGroup.name]
|
||||
theStatic.payload = theUnit.payload -- not supported (yet) by DCS
|
||||
theStatic.onboard_num = theUnit.onboard_num -- not supported
|
||||
-- DML only: allNeutral
|
||||
if stopGap.allNeutral then
|
||||
theStatic.cty = dcsCommon.getACountryForCoalition(0)
|
||||
@ -499,6 +504,4 @@ if not stopGap.start() then
|
||||
trigger.action.outText("+++ aborted stopGap v" .. stopGap.version .. " -- startup failed", 30)
|
||||
stopGap = nil
|
||||
end
|
||||
--[[-- TODO
|
||||
- allNeutral: spawn all player aircraft as neutral
|
||||
--]]--
|
||||
|
||||
|
||||
76
modules/twn.lua
Normal file
76
modules/twn.lua
Normal 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()
|
||||
@ -1,5 +1,5 @@
|
||||
valet = {}
|
||||
valet.version = "1.1.0"
|
||||
valet.version = "1.1.1"
|
||||
valet.verbose = false
|
||||
valet.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -14,6 +14,7 @@ valet.valets = {}
|
||||
1.0.2 - also scan birth events
|
||||
1.0.3 - outSoundFile now working correctly
|
||||
1.1.0 - hysteresis is now time-based (10 seconds)
|
||||
1.1.1 - hardening against DCS July-11 update issues
|
||||
--]]--
|
||||
|
||||
function valet.addValet(theZone)
|
||||
@ -361,6 +362,7 @@ function valet.checkPlayerSpawn(playerName, theUnit)
|
||||
-- see if player spawned in a valet zone
|
||||
if not playerName then return end
|
||||
if not theUnit then return end
|
||||
if not theUnit.getName then return end
|
||||
|
||||
local pos = theUnit:getPoint()
|
||||
--trigger.action.outText("+++valet: spawn event", 30)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
williePete = {}
|
||||
williePete.version = "2.0.5"
|
||||
williePete.version = "2.1.0"
|
||||
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
|
||||
-- missile moving at Mach 2 is within 33 meters,
|
||||
-- with interpolation even at 3 meters
|
||||
@ -22,6 +22,8 @@ williePete.requiredLibs = {
|
||||
2.0.3 - further hardened playerUpdate()
|
||||
2.0.4 - support for the Kiowa's Hydra M259
|
||||
2.0.5 - support for Mirage F1 WP that differ from Gazelle (?)
|
||||
2.0.6 - DCS Update 7-11 2024 weapon name bug
|
||||
2.1.0 - DCS update 2.9.6 dynamic spawn support
|
||||
--]]--
|
||||
|
||||
williePete.willies = {}
|
||||
@ -134,6 +136,53 @@ end
|
||||
--
|
||||
-- PLAYER MANAGEMENT
|
||||
--
|
||||
function williePete.latePlayerGUI(theUnit)
|
||||
local unitInfo = {}
|
||||
unitInfo.name = theUnit:getName() -- needed for reverse-lookup
|
||||
local theGroup = theUnit:getGroup()
|
||||
unitInfo.gName = theGroup:getName() -- gName -- also needed for reverse lookup
|
||||
unitInfo.coa = theGroup:getCoalition() -- coa
|
||||
unitInfo.gID = theGroup:getID() -- gData.groupId
|
||||
unitInfo.uID = theUnit:getID() -- uData.unitId
|
||||
unitInfo.theType = theUnit:getTypeName() -- theType
|
||||
cat = theGroup:getCategory()
|
||||
if cat == 0 then unitInfo.cat = "plane"
|
||||
elseif cat == 1 then unitInfo.cat = "helicopter"
|
||||
else
|
||||
return -- whatever player is controlling, it's not for WP
|
||||
end
|
||||
|
||||
williePete.doGUIforUnitInfo(unitInfo)
|
||||
end
|
||||
|
||||
function williePete.doGUIforUnitInfo(unitInfo)
|
||||
local pass = false
|
||||
local uName = unitInfo.name
|
||||
local gName = unitInfo.gName
|
||||
|
||||
for idx, aType in pairs(williePete.facTypes) do
|
||||
if aType == "ALL" then pass = true end
|
||||
if aType == "ANY" then pass = true end
|
||||
if aType == theType then pass = true end
|
||||
if dcsCommon.stringStartsWith(aType, "HEL") and unitInfo.cat == "helicopter" then pass = true end
|
||||
if dcsCommon.stringStartsWith(aType, "PLAN") and unitInfo.cat == "plane" then pass = true end
|
||||
end
|
||||
|
||||
if pass then -- we install a menu for this group
|
||||
-- we may not want check in stuff, but it could be cool
|
||||
if williePete.playerGUIs[gName] then
|
||||
trigger.action.outText("+++WP: Warning: we already have WP menu for unit <" .. uName .. "> in group <" .. gName .. ">. Skipped.", 30)
|
||||
elseif williePete.groupGUIs[gName] then
|
||||
trigger.action.outText("+++WP: Warning: POSSIBLE MULTI-PLAYER UNIT GROUP DETECTED. We already have WP menu for Player Group <" .. gName .. ">. Skipped, only first unit supported. ", 30)
|
||||
else
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
|
||||
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
|
||||
williePete.groupGUIs[gName] = unitInfo
|
||||
williePete.playerGUIs[gName] = unitInfo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function williePete.startPlayerGUI()
|
||||
-- scan all mx players
|
||||
-- note: currently assumes single-player groups
|
||||
@ -159,7 +208,7 @@ function williePete.startPlayerGUI()
|
||||
unitInfo.theType = theType
|
||||
unitInfo.cat = cfxMX.groupTypeByName[gName]
|
||||
-- now check type against willie pete config for allowable types
|
||||
local pass = false
|
||||
--[[-- local pass = false
|
||||
for idx, aType in pairs(williePete.facTypes) do
|
||||
if aType == "ALL" then pass = true end
|
||||
if aType == "ANY" then pass = true end
|
||||
@ -181,7 +230,8 @@ function williePete.startPlayerGUI()
|
||||
williePete.playerGUIs[gName] = unitInfo
|
||||
end
|
||||
end
|
||||
|
||||
--]]--
|
||||
williePete.doGUIforUnitInfo(unitInfo)
|
||||
-- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups
|
||||
--williePete.playerGUIs[uName] = unitInfo
|
||||
end
|
||||
@ -467,7 +517,7 @@ end
|
||||
|
||||
function williePete.zedsDead(theObject)
|
||||
if not theObject then return end
|
||||
|
||||
if not theObject.getName then return end -- DCS July-11 oddity.
|
||||
local theName = theObject:getName()
|
||||
-- now check if it's a registered blasted object:getSampleRate()
|
||||
-- in multi-unit player groups, this can can lead to
|
||||
@ -493,6 +543,14 @@ function williePete:onEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
-- see if a dynamic spawn
|
||||
if event.id == 15 then
|
||||
local theUnit = event.initiator
|
||||
if not cfxMX.isDynamicPlayer(theUnit) then return end
|
||||
williePete.latePlayerGUI(theUnit)
|
||||
return
|
||||
end
|
||||
|
||||
-- check if it's a dead event
|
||||
if event.id == 8 then
|
||||
-- death event
|
||||
|
||||
Binary file not shown.
BIN
tutorial & demo missions/demo - escort a convoy to town.miz
Normal file
BIN
tutorial & demo missions/demo - escort a convoy to town.miz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user