Yeller Model, The Debugger -x !, heloTroops' dropZones
This commit is contained in:
Christian Franz 2024-11-07 07:35:36 +01:00
parent 0c1eb53a89
commit b498a4e803
13 changed files with 506 additions and 121 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
cfxCargoReceiver = {}
cfxCargoReceiver.version = "2.0.0"
cfxCargoReceiver.version = "2.1.0"
cfxCargoReceiver.ups = 1 -- once a second
cfxCargoReceiver.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo
cfxCargoReceiver.requiredLibs = {
@ -12,7 +12,10 @@ cfxCargoReceiver.requiredLibs = {
- 2.0.0 no more cfxPlayer Dependency
dmlZones, OOP
clean-up
- 2.1.0 code maintenance
cargo gets removed from mgr when touching ground
switch to cargoReceiver!
noBounce attribute
CargoReceiver is a zone enhancement you use to be automatically
notified if a cargo was delivered inside the zone.
@ -25,26 +28,30 @@ cfxCargoReceiver.receiverZones = {}
function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and add to zone
-- since the attribute is there, simply set the zones
-- isCargoReceiver flag and we are good
aZone.isCargoReceiver = true
-- we can add additional processing here
aZone.autoRemove = aZone:getBoolFromZoneProperty("autoRemove", false) -- maybe add a removeDelay
aZone.isCargoReceiver = true -- so all zones can detect this. Necessary?
aZone.autoRemove = aZone:getBoolFromZoneProperty("autoRemove", false)
aZone.removeDelay = aZone:getNumberFromZoneProperty("removeDelay", 1)
if aZone.removeDelay < 1 then aZone.removeDelay = 1 end
aZone.silent = aZone:getBoolFromZoneProperty("silent", false)
-- new method support
aZone.cargoMethod = aZone:getStringFromZoneProperty("method", "inc")
if aZone:hasProperty("cargoMethod") then
aZone.cargoMethod = aZone:getStringFromZoneProperty("cargoMethod", "inc")
end
if aZone:hasProperty("f!") then
-- save the names of cargos delivered to prevent 'bounce'
aZone.cargosDelivered = {} -- by cargo name
aZone.noBounce = aZone:getBoolFromZoneProperty("noBounce", true)
if aZone:hasProperty("cargoReceiver!") then
aZone.outReceiveFlag = aZone:getStringFromZoneProperty("cargoReceiver!", "*<none>")
elseif aZone:hasProperty("f!") then
aZone.outReceiveFlag = aZone:getStringFromZoneProperty("f!", "*<none>")
elseif aZone:hasProperty("cargoReceived!") then
aZone.outReceiveFlag = aZone:getStringFromZoneProperty( "cargoReceived!", "*<none>")
end
if not aZone.outReceiveFlag then
trigger.action.outText("+++crgR: receiver <" .. aZone.name .. "> has no output!", 30)
end
end
function cfxCargoReceiver.addReceiverZone(aZone)
@ -94,7 +101,7 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
if not event then return end
if event == "grounded" then
-- this is actually the only one that interests us
-- this is the only one that interests us
if not object then
return
end
@ -106,21 +113,28 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
-- now invoke callbacks for all zones
-- this is in
for name, aZone in pairs(cfxCargoReceiver.receiverZones) do
if cfxZones.pointInZone(loc, aZone) then
if aZone:pointInZone(loc) then
cfxCargoReceiver.invokeCallback("deliver", object, name, aZone)
-- set flags as indicated
if aZone.outReceiveFlag then
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod, aZone)
if aZone.noBounce then
-- we only do this once for every named object
if not aZone.cargosDelivered[name] then
aZone.cargosDelivered[name] = true
aZone:pollFlag(aZone.outReceiveFlag, aZone.cargoMethod)
end
else
aZone:pollFlag(aZone.outReceiveFlag, aZone.cargoMethod)
end
end
if aZone.autoRemove then
-- schedule this for in a few seconds?
-- schedule this for in a few seconds
local args = {}
args.theObject = object
args.theZone = aZone
timer.scheduleFunction(cfxCargoReceiver.removeCargo, args, timer.getTime() + aZone.removeDelay)
--object:destroy()
end
end
end
@ -139,17 +153,13 @@ function cfxCargoReceiver.update()
-- new we see if any of these are close to a delivery zone
for idx, aCargo in pairs(liftedCargos) do
local thePoint = aCargo:getPoint()
local receiver = cfxZones.getClosestZone(
thePoint,
cfxCargoReceiver.receiverZones -- must be indexed by name
)
local receiver = cfxZones.getClosestZone(thePoint, cfxCargoReceiver.receiverZones) -- must be indexed by name
-- we now check if we are in 'speaking range' and receiver can talk
-- modify delta by distance to boundary, not
-- center
-- modify delta by distance to boundary, not center
local delta = dcsCommon.distFlat(thePoint, cfxZones.getPoint(receiver))
delta = delta - receiver.radius
if (receiver.silent == false) and
if (not receiver.silent) and
(delta < cfxCargoReceiver.maxDirectionRange) then
-- this cargo can be talked down.
-- find the player unit that is closest to in in hopes
@ -214,10 +224,14 @@ function cfxCargoReceiver.start()
end
-- scan all zones for cargoReceiver flag
local attrZones = cfxZones.getZonesWithAttributeNamed("cargoReceiver!")
for k, aZone in pairs(attrZones) do
cfxCargoReceiver.processReceiverZone(aZone)
cfxCargoReceiver.addReceiverZone(aZone)
end
-- old version, LEGACY
local attrZones = cfxZones.getZonesWithAttributeNamed("cargoReceiver")
-- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not
-- paused
for k, aZone in pairs(attrZones) do
cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and add to zone
cfxCargoReceiver.addReceiverZone(aZone) -- remember it so we can smoke it

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.4.2"
cfxZones.version = "4.4.3"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -32,6 +32,7 @@ cfxZones.version = "4.4.2"
- dmlZone:getCoalition() dereferences masterOwner once
-4.4.1 - better verbosity for error in doPollFlag()
-4.4.2 - twn support for wildcards <twn: > and <loc:>
-4.4.3 - property name is trimmed (double check)
--]]--
--
@ -123,7 +124,14 @@ function cfxZones.readFromDCS(clearfirst)
else
newZone.properties = {}
end -- WARNING: REF COPY. May need to clone
--[[--
trigger.action.outText("zone <> properties:trimmed", 30)
local msg = "["
for idx, val in pairs(newZone.properties) do
msg = msg .. "<" .. val.key .. ">:<" .. dcsCommon.trim(val.key) .. ">, "
end
trigger.action.outText(msg, 30)
--]]--
local upperName = newZone.name:upper()
-- location as 'point'
@ -2158,7 +2166,14 @@ function cfxZones.extractPropertyFromDCS(theKey, theProperties)
for i=1, #theProperties do
local theP = theProperties[i]
local existingKey = dcsCommon.trim(theP.key)
local existingKey = dcsCommon.trim(theP.key) -- does not work if ends on "#!?" - why?
--while len(existingKey) > 1 and string.sub(existingKey, -1) == " " do
-- existingKey = existingKey:sub(1, -2) -- trim manually
--end
if string.sub(existingKey, -1) == " " then
trigger.action.outText("+++ZONES: warning: unable to trim blanks from attribute name <" .. existingKey .. ">", 30)
end
-- trigger.action.outText("procced attribute name <" .. existingKey .. ">", 30)
if not cfxZones.caseSensitiveProperties then
existingKey = string.lower(existingKey)
end
@ -2388,6 +2403,7 @@ function dmlZone:hasProperty(theProperty)
trigger.action.outText("+++zne: WARNING - hasProperty called with nil theProperty for zone <" .. self.name .. ">", 30)
return false
end
theProperty = dcsCommon.trim(theProperty) -- strip leading and training blanks
local foundIt = self:getZoneProperty(theProperty)
if not foundIt then
-- check for possible forgotten or exchanged IO flags

View File

@ -1,5 +1,5 @@
civAir = {}
civAir.version = "3.0.2"
civAir.version = "3.1.0"
--[[--
3.0.0 liveries support
default liveries for Yak-50 (main test case)
@ -15,7 +15,12 @@ civAir.version = "3.0.2"
protest action
spawning now works correctly for groupType
3.0.2 clean-up
3.1.0 better verbosity for startup info
dicts for departOnly and landingOnly
correctly filter depart only, land only, closed at start
Syria multiple "H" airfields DCS bug
filter airfields with no runways
getAirfieldNamed
--]]--
civAir.ups = 0.05 -- updates per second. 0.05 = once every 20 seconds
@ -60,8 +65,11 @@ civAir.maxIdle = 8 * 60 -- seconds of ide time before it is removed after landin
civAir.trafficCenters = {}
civAir.excludeAirfields = {}
civAir.excludeAirfieldsDict = {}
civAir.departOnly = {} -- use only to start from
civAir.departOnlyDict = {} -- as above, by af name
civAir.landingOnly = {} -- use only to land at
civAir.landingOnlyDict = {} -- as above, by af name
civAir.inoutZones = {} -- off-map connector zones
civAir.requiredLibs = {
@ -180,7 +188,7 @@ end
function civAir.processZone(theZone)
local value = theZone:getStringFromZoneProperty("civAir", "")
local af = dcsCommon.getClosestAirbaseTo(theZone.point, 0) -- 0 = only airfields, not farp or ships
local af = dcsCommon.getClosestAirbaseTo(theZone.point, 0) -- 0 = only airfields, not farp nor ships
local inoutName = "***" .. theZone:getName()
if af then
@ -188,10 +196,13 @@ function civAir.processZone(theZone)
value = value:lower()
if value == "exclude" or value == "closed" then
table.insert(civAir.excludeAirfields, afName)
civAir.excludeAirfieldsDict[afName] = afName
elseif dcsCommon.stringStartsWith(value, "depart") or dcsCommon.stringStartsWith(value, "start") or dcsCommon.stringStartsWith(value, "take") then
table.insert(civAir.departOnly, afName)
civAir.departOnlyDict[afName] = afName -- can fold many in one
elseif dcsCommon.stringStartsWith(value, "land") or dcsCommon.stringStartsWith(value, "arriv") then
table.insert(civAir.landingOnly, afName)
civAir.landingOnlyDict[afName] = afName -- can fold many on one
elseif dcsCommon.stringStartsWith(value, "inb") then
table.insert(civAir.departOnly, inoutName) -- start in inbound zone
civAir.inoutZones[inoutName] = theZone
@ -253,8 +264,8 @@ function civAir.filterAirfields(inAll, inFilter)
end
function civAir.getTwoAirbases()
local fAB -- first airbase to depart
local sAB -- second airbase to fly to
local fAB -- name of first airbase to depart from
local sAB -- name second airbase to fly to
local departAB = dcsCommon.combineTables(civAir.trafficCenters, civAir.departOnly)
-- remove all currently excluded air bases from departure
@ -263,11 +274,14 @@ function civAir.getTwoAirbases()
if #filteredAB < 1 then
trigger.action.outText("+++civA: too few departure airfields", 30)
return nil, nil
else
-- trigger.action.outText("+++civA: source (first) fields available: " .. #filteredAB, 30)
end
-- now pick the departure airfield
fAB = dcsCommon.pickRandom(filteredAB)
fAB = dcsCommon.pickRandom(filteredAB) -- returns an airfield name
-- trigger.action.outText("+++civA: picked first: <" .. fAB .. ">", 30)
-- now generate list of landing airfields
local arriveAB = dcsCommon.combineTables(civAir.trafficCenters, civAir.landingOnly)
-- remove all currently excluded air bases from arrival
@ -277,6 +291,8 @@ function civAir.getTwoAirbases()
if #filteredAB < 1 then
trigger.action.outText("+++civA: too few arrival airfields", 30)
return nil, nil
else
-- trigger.action.outText("+++civA: second (ARV) fields available: " .. #filteredAB, 30)
end
-- pick any second that are not the same
@ -285,10 +301,13 @@ function civAir.getTwoAirbases()
sAB = dcsCommon.pickRandom(filteredAB)
tries = tries + 1 -- only try 10 times
until fAB ~= sAB or tries > 10
-- trigger.action.outText("+++civA: picked SECOND: <" .. sAB .. ">", 30)
local civA = {}
if not (dcsCommon.stringStartsWith(fAB, '***')) then
civA.AB = dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0)
civA.AB = civAir.getAirfieldNamed(fAB) -- dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0)
civA.name = civA.AB:getName()
else
civA.zone = civAir.inoutZones[fAB]
@ -296,16 +315,22 @@ function civAir.getTwoAirbases()
end
local civB = {}
if not (dcsCommon.stringStartsWith(sAB, '***')) then
civB.AB = dcsCommon.getFirstAirbaseWhoseNameContains(sAB, 0)
civB.AB = civAir.getAirfieldNamed(sAB) -- dcsCommon.getFirstAirbaseWhoseNameContains(sAB, 0)
civB.name = civB.AB:getName()
else
civB.zone = civAir.inoutZones[sAB]
civB.name = civB.zone:getName()
end
-- trigger.action.outText("+++civA: picked source AF <" .. civA.name .. "> and dest <" .. civB.name .. ">", 30)
return civA, civB -- fAB, sAB
end
function civAir.getAirfieldNamed(name)
return Airbase.getByName(name)
end
function civAir.parkingIsFree(fromWP)
-- iterate over all currently registred flights and make
-- sure that their location isn't closer than 10m to my new parking
@ -684,24 +709,48 @@ function civAir.collectHubs()
end
function civAir.listTrafficCenters()
trigger.action.outText("Traffic Centers", 30)
local msg = "Traffic Centers:\n"
local other = false
for idx, aName in pairs(civAir.trafficCenters) do
trigger.action.outText(aName, 30)
if other then msg = msg .. ", " end
msg = msg .. aName
other = true
end
trigger.action.outText(msg .. "\n", 30)
msg = "Departure-Only:\n"
other = false
if #civAir.departOnly > 0 then
trigger.action.outText("Departure-Only:", 30)
for idx, aName in pairs(civAir.departOnly) do
trigger.action.outText(aName, 30)
if other then msg = msg .. ", " end
msg = msg .. aName
other = true
end
end
trigger.action.outText(msg .. "\n", 30)
msg = "Landing-Only:\n"
other = false
if #civAir.landingOnly > 0 then
trigger.action.outText("Arrival/Landing-Only:", 30)
for idx, aName in pairs(civAir.landingOnly) do
trigger.action.outText(aName, 30)
if other then msg = msg .. ", " end
msg = msg .. aName
other = true
end
end
trigger.action.outText(msg .. "\n", 30)
msg = "Closed:\n"
other = false
if #civAir.excludeAirfields > 0 then
for idx, aName in pairs(civAir.excludeAirfields) do
if other then msg = msg .. ", " end
msg = msg .. aName
other = true
end
end
trigger.action.outText(msg .. "\n", 30)
end
-- start
@ -721,13 +770,27 @@ function civAir.start()
if (#civAir.trafficCenters + #civAir.departOnly < 1) or
(#civAir.trafficCenters + #civAir.landingOnly < 1)
then
trigger.action.outText("+++civA: marked traffic centers:" .. #civAir.trafficCenters .. "\n depart-only:" .. #civAir.departOnly .. "\n landing-only: " .. #civAir.landingOnly .. "\n", 30 )
trigger.action.outText("+++civA: auto-populating", 30)
-- simply add airfields on the map
-- and filter those that are excluded, land-only or depart only
local allBases = dcsCommon.getAirbasesWhoseNameContains("*", 0)
for idx, aBase in pairs(allBases) do
local afName = aBase:getName()
table.insert(civAir.trafficCenters, afName)
if civAir.departOnlyDict[afName] or
civAir.landingOnlyDict[afName] or
civAir.excludeAirfieldsDict[afName]
then
trigger.action.outText("+++civA: filtering base " .. afName .. " for single-exception", 30)
else
-- syria special proccing
local rw = aBase:getRunways()
if #rw < 1 then
-- trigger.action.outText(afName .. " has no runways, filtered", 30)
else
table.insert(civAir.trafficCenters, afName)
end
end
end
end

View File

@ -1,5 +1,5 @@
convoy = {}
convoy.version = "1.1.0"
convoy.version = "1.2.0"
convoy.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -32,7 +32,8 @@ VERSION HISTORY
- warning when not onStart and no start?
- say hi
- removed destination attribute
1.2.0 - convoyMethod
- convoyTriggerMethod
--]]--
--[[-- CONVOY Structure
@ -174,6 +175,9 @@ function convoy.readConvoyZone(theZone)
trigger.action.outText("+++CVY: Warning: Convoy zone <" .. theZone.name .. "> has disabled 'onStart' and has no 'spawn?' input. This convoy zone can't send out any convoys,", 30)
end
end
theZone.convoyMethod = theZone:getStringFromZoneProperty("convoyMethod", "inc")
theZone.convoyTriggerMethod = theZone:getStringFromZoneProperty("convoyTriggerMethod", "change")
--[[--
if theZone:hasProperty("destination") then -- remove me
theZone.destination = theZone:getStringFromZoneProperty("destination", "none")
@ -560,7 +564,7 @@ function convoy.wpReached(gName, convName, idx, wpNum)
convoy.invokeArrivedCallbacks(theConvoy)
-- hit the output flag if defined
if theZone.arrivedOut then
theZone:pollFlag(theZone.arrivedOut, "inc")
theZone:pollFlag(theZone.arrivedOut, theZone.convoyMethod) -- "inc")
end
-- remove convoy from watchlist
convoy.convoys[convName] = nil
@ -845,7 +849,7 @@ function convoy.update()
-- check for flags
for idx, theZone in pairs (convoy.zones) do
if theZone.spawnFlag and
theZone:testZoneFlag(theZone.spawnFlag, "change", "lastSpawnFlag") then
theZone:testZoneFlag(theZone.spawnFlag, theZone.convoyTriggerMethod, "lastSpawnFlag") then
convoy.startConvoy(theZone)
end
end
@ -900,7 +904,7 @@ function convoy.statusUpdate() -- every 10 seconds
convoy.invokeAttackedCallbacks(theConvoy)
theZone = theConvoy.origin
if theZone.attackedOut then
theZone:pollFlag(theZone.attackedOut, "inc")
theZone:pollFlag(theZone.attackedOut, theZone.convoyMethod) -- "inc")
end
end
@ -909,7 +913,7 @@ function convoy.statusUpdate() -- every 10 seconds
convoy.invokeDestroyedCallbacks(theConvoy)
theZone = theConvoy.origin
if theZone.deadOut then
theZone:pollFlag(theZone.deadOut, "inc")
theZone:pollFlag(theZone.deadOut, theZone.convoyMethod) -- "inc")
end
trigger.action.outTextForCoalition(theConvoy.coa, "Convoy " .. convName .. " enroute to " .. theConvoy.dest .. " was destroyed.", 30)
trigger.action.outSoundForCoalition(theConvoy.coa, convoy.actionSound)

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.1.5"
cfxHeloTroops.version = "4.0.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -25,6 +25,9 @@ cfxHeloTroops.requestRange = 500 -- meters
- loadSound and disembarkSound
3.1.4 - guarding destination access in save
3.1.5 - more guarding of destination access
4.0.0 - added dropZones
- enforceDropZones
- coalition for drop zones
--]]--
@ -37,10 +40,24 @@ cfxHeloTroops.requiredLibs = {
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper
cfxHeloTroops.dropZones = {}
-- persistence support
cfxHeloTroops.deployedTroops = {}
--
-- drop zones
--
function cfxHeloTroops.processDropZone(theZone)
theZone.droppedFlag = theZone:getStringFromZoneProperty("dropZone!", "cfxNone")
theZone.dropMethod = theZone:getStringFromZoneProperty("dropMethod", "inc")
theZone.dropCoa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.autoDespawn = theZone:getNumberFromZoneProperty("autoDespawn", -1)
end
--
-- comms
--
function cfxHeloTroops.resetConfig(conf)
conf.autoDrop = cfxHeloTroops.autoDrop --if true, will drop troops on-board upon touchdown
conf.autoPickup = cfxHeloTroops.autoPickup -- if true will load nearest troops upon touchdown
@ -576,16 +593,33 @@ function cfxHeloTroops.scoreWhenCapturing(theUnit)
end
end
function cfxHeloTroops.isInsideDropZone(theUnit)
local p = theUnit:getPoint()
for idx, theZone in pairs (cfxHeloTroops.dropZones) do
if theZone:isPointInsideZone(p) then return true end
end
return false
end
function cfxHeloTroops.doDeployTroops(args)
local conf = args[1]
local what = args[2]
-- deploy the troops I have on board in formation
local theUnit = conf.unit
local theGroup = theUnit:getGroup()
local gid = theGroup:getID()
local inside = cfxHeloTroops.isInsideDropZone(theUnit)
if (not inside) and cfxHeloTroops.enforceDropZones then
trigger.action.outTextForGroup(gid, "You are outside an disembark/drop zone.", 30)
return
end
-- deploy the troops I have on board
cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- interface with playerscore if we dropped
-- inside an enemy-owned zone
if cfxPlayerScore and cfxOwnedZones then
local theUnit = conf.unit
--local theUnit = conf.unit
cfxHeloTroops.scoreWhenCapturing(theUnit)
end
@ -630,7 +664,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
end
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa
local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION
local theCoalition = theUnit:getGroup():getCoalition() -- make it chopper's COALITION
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theName, -- group name, may be tracked
@ -677,8 +711,39 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
end
end
end
-- bang on all dropZones that we can find
for name, theZone in pairs(cfxHeloTroops.dropZones) do
-- can employ coalition test here as well, maybe later?
if theZone:isPointInsideZone(p) then
if theZone.dropCoa == 0 or theCoalition == theZone.dropCoa then
if cfxHeloTroops.verbose or theZone.verbose then
trigger.action.outText("+++Helo: will bang! on dropZone <" .. theZone.name .. "> output dropZone! <" .. theZone.droppedFlag .. "> with method <" .. theZone.dropMethod .. ">", 30)
end
theZone:pollFlag(theZone.droppedFlag, theZone.dropMethod)
end
if theZone.autoDespawn and theZone.autoDespawn > 0 then
args = {}
args.theZone = theZone
args.theGroup = theGroup
timer.scheduleFunction(cfxHeloTroops.autoDespawn, args, timer.getTime() + theZone.autoDespawn)
end
end
end
end
function cfxHeloTroops.autoDespawn(args)
if not args then return end
local theZone = args.theZone
local theGroup = args.theGroup
if theZone.verbose then
trigger.action.outText("+++Helo: auto-despawning drop in drop zone <" .. theZone.name .. ">", 30)
end
if not theGroup then return end
if Group.isExist(theGroup) then
Group.destroy(theGroup)
end
end
--
-- Loading Troops
--
@ -882,7 +947,7 @@ function cfxHeloTroops.readConfigZone()
theZone = cfxZones.createSimpleZone("heloTroopsConfig")
end
cfxHeloTroops.verbose = theZone:getBoolFromZoneProperty("verbose", false)
cfxHeloTroops.verbose = theZone.verbose
if theZone:hasProperty("legalTroops") then
local theTypesString = theZone:getStringFromZoneProperty("legalTroops", "")
@ -907,6 +972,7 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.disembarkSound = theZone:getStringFromZoneProperty("disembarkSound", cfxHeloTroops.actionSound)
cfxHeloTroops.requestRange = theZone:getNumberFromZoneProperty("requestRange", 500)
cfxHeloTroops.enforceDropZones = theZone:getBoolFromZoneProperty("enforceDropZones", false)
-- add own troop carriers
if theZone:hasProperty("troopCarriers") then
local tc = theZone:getStringFromZoneProperty("troopCarriers", "UH-1D")
@ -999,6 +1065,13 @@ function cfxHeloTroops.start()
-- read config zone
cfxHeloTroops.readConfigZone()
-- read drop zones
local attrZones = cfxZones.getZonesWithAttributeNamed("dropZone!")
for k, aZone in pairs(attrZones) do
cfxHeloTroops.processDropZone(aZone)
cfxHeloTroops.dropZones[aZone.name] = aZone
end
-- start housekeeping
cfxHeloTroops.houseKeeping()

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "3.3.1"
cfxPlayerScore.version = "4.0.0"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
@ -18,7 +18,9 @@ cfxPlayerScore.firstSave = true -- to force overwrite
3.3.0 - case INsensitivity for all typeScore objects
3.3.1 - fixes for DCS oddity in events after update
- cleanup
4.0.0 - own event handling, disco from dcsCommon
- early landing detection (unitSpawnTime)
TODO: Kill event no longer invoked for map objetcs, attribute
to faction now, reverse invocation direction with PlayerScore
--]]--
@ -51,7 +53,7 @@ cfxPlayerScore.train = 5
cfxPlayerScore.landing = 0 -- if > 0 it scores as feat
cfxPlayerScore.unit2player = {} -- lookup and reverse look-up
cfxPlayerScore.unitSpawnTime = {} -- lookup by unit name to prevent early landing
function cfxPlayerScore.addSafeZone(theZone)
theZone.scoreSafe = theZone:getCoalitionFromZoneProperty("scoreSafe", 0)
table.insert(cfxPlayerScore.safeZones, theZone)
@ -570,7 +572,7 @@ function cfxPlayerScore.awardScoreTo(killSide, theScore, killerName)
end
--
-- EVENT HANDLING
-- EVENT PROCESSING / HANDLING
--
function cfxPlayerScore.linkUnitWithPlayer(theUnit)
-- create the entries for lookup and reverseLooup tables
@ -583,7 +585,7 @@ function cfxPlayerScore.unlinkUnit(theUnit)
local uName = theUnit:getName()
cfxPlayerScore.unit2player[uName] = nil
end
--[[--
function cfxPlayerScore.preProcessor(theEvent)
-- return true if the event should be processed
-- by us
@ -721,10 +723,13 @@ function cfxPlayerScore.preProcessor(theEvent)
return false
end
--]]--
--[[--
function cfxPlayerScore.postProcessor(theEvent)
-- don't do anything
end
--]]--
function cfxPlayerScore.isStaticObject(theUnit)
if not theUnit.getGroup then return true end
@ -1091,7 +1096,165 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
end
end
function cfxPlayerScore.handlePlayerEvent(theEvent)
--
-- event detection
--
function cfxPlayerScore.isScoreEvent(theEvent)
-- return true if the event results in a score event
if theEvent.initiator == nil then
return false
end
if cfxPlayerScore.verbose then
trigger.action.outText("Event preproc: " .. theEvent.id .. " (" .. dcsCommon.event2text(theEvent.id) .. ")", 30)
if theEvent.id == 8 or theEvent.id == 30 then -- dead or lost event
local who = theEvent.initiator
local name = "(nil ini)"
if who then
name = "(inval object)"
if who.getName then name = who:getName() end
end
trigger.action.outText("Dead/Lost subject: <" .. name .. ">", 30)
end
if theEvent.id == 2 then -- hit
local who = theEvent.initiator
local name = "(nil ini)"
if who then
name = "(inval initi)"
if who.getName then name = who:getName() end
if not name then -- WTF??? could be a weapon
name = "!nil getName!"
if who.getTypeName then name = who:getTypeName() end
if not name then
name = "WTFer"
end
end
end
local hit = theEvent.object
local hname = "(nil ini)"
if hit then
hname = "(inval object)"
if hit.getName then hname = hit:getName() end
end
trigger.action.outText("o:<" .. name .. "> hit <" .. hname .. ">", 30)
end
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
-- one of the below
if theEvent.id == 5 or -- crash
theEvent.id == 8 or -- dead
theEvent.id == 9 or -- pilot_dead
theEvent.id == 30 or -- unit loss
theEvent.id == 6 then -- eject
-- these can lead to a pilot demerit
-- event does NOT have a player
cfxPlayerScore.handlePlayerDeath(theEvent)
return false -- false = no score event (any more)
end
end
-- from here on, initiator must be player
if not theUnit.getPlayerName or
not theUnit:getPlayerName() then
return false
end
if theEvent.id == 28 then -- kill, but only with target
local killer = theEvent.initiator
if not theEvent.target then
if cfxPlayerScore.verbose then
trigger.action.outText("+++scr kill nil TARGET", 30)
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
trigger.action.outText("+++pScr: kill detected, but player <" .. theUnit:getPlayerName() .. "> was outside of kill zone <" .. theZone.name .. ">", 30)
end
return false
end
end
return true
end
-- birth event for players initializes score if
-- not existed, and nils the queue
if theEvent.id == 15 then -- player birth
-- link player and unit
cfxPlayerScore.linkUnitWithPlayer(theUnit)
cfxPlayerScore.unitSpawnTime[uName] = timer.getTime() -- to detect 'early landing'
-- trigger.action.outText("Birth event", 30)
return true
end
-- take off. overwrites timestamp for last landing
-- so a blipping t/o does nor count. Pre-proc only
if theEvent.id == 3 or theEvent.id == 54 then
local now = timer.getTime()
local playerName = theUnit:getPlayerName()
cfxPlayerScore.lastPlayerLanding[playerName] = now -- overwrite
return false
end
-- landing can score. but only the first landing in x seconds
-- and has spawned more than 10 seconds before
-- landing in safe zone promotes any queued scores to
-- permanent if enabled, then nils queue
if theEvent.id == 4 or theEvent.id == 55 then
-- trigger.action.outText("LANDING event", 30)
-- player landed. filter multiple landed events
local now = timer.getTime()
local playerName = theUnit:getPlayerName()
-- if player spawns on ground, DCS now can post a
-- "landing" event. filter
if cfxPlayerScore.unitSpawnTime[uName] and
now - cfxPlayerScore.unitSpawnTime[uName] < 10
then
cfxPlayerScore.lastPlayerLanding[playerName] = now -- just for the sake of it
-- trigger.action.outText("(DCS early landing bug ignored)", 30)
return false
end
-- trigger.action.outText("Time since spawn: " .. now - cfxPlayerScore.unitSpawnTime[uName], 30)
local lastLanding = cfxPlayerScore.lastPlayerLanding[playerName]
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 after last.", 30)
trigger.action.outText("now is <" .. now .. ">, between is <" .. cfxPlayerScore.delayBetweenLandings .. ">, last + between is <" .. lastLanding + cfxPlayerScore.delayBetweenLandings .. ">", 30)
end
-- filter this event, too soon
return false
end
return true
end
return false
end
function cfxPlayerScore:onEvent(theEvent)
if cfxPlayerScore.isScoreEvent(theEvent) then
cfxPlayerScore.handleScoreEvent(theEvent)
end
end
function cfxPlayerScore.handleScoreEvent(theEvent)
if theEvent.id == 28 then
-- kill from player detected.
cfxPlayerScore.killDetected(theEvent)
@ -1134,7 +1297,9 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
end
end
end
--
-- Config handling
--
function cfxPlayerScore.readConfigZone(theZone)
cfxPlayerScore.verbose = theZone.verbose
-- default scores
@ -1450,9 +1615,10 @@ function cfxPlayerScore.start()
end
-- subscribe to events and use dcsCommon's handler structure
dcsCommon.addEventHandler(cfxPlayerScore.handlePlayerEvent,
cfxPlayerScore.preProcessor,
cfxPlayerScore.postProcessor)
-- dcsCommon.addEventHandler(cfxPlayerScore.handlePlayerEvent,
-- cfxPlayerScore.preProcessor,
-- cfxPlayerScore.postProcessor)
world.addEventHandler(cfxPlayerScore)
-- now load all save data and populate map with troops that
-- we deployed last save.
if persistence then

View File

@ -21,6 +21,7 @@ radioMenu.lateGroups = {} -- dict by ID
- added dynamic player support
4.0.1 - MX no longer optional, so ask for it
4.0.2 - ackSnd now also broadcasts to all if no group
4.1.0 - outX --> valX verification. putting back in optional outX
--]]--
function radioMenu.addRadioMenu(theZone)
@ -437,6 +438,11 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("radioMethod") then
theZone.radioMethod = theZone:getStringFromZoneProperty( "radioMethod", "inc")
end
-- note: outX currently overridden with valX
theZone.outMethA = theZone:getStringFromZoneProperty("outA", theZone.radioMethod)
theZone.outMethB = theZone:getStringFromZoneProperty("outB", theZone.radioMethod)
theZone.outMethC = theZone:getStringFromZoneProperty("outC", theZone.radioMethod)
theZone.outMethD = theZone:getStringFromZoneProperty("outD", theZone.radioMethod)
theZone.radioTriggerMethod = theZone:getStringFromZoneProperty("radioTriggerMethod", "change")
@ -731,6 +737,7 @@ function radioMenu.doMenuX(args)
local outVal = theZone.outValA
local ack = theZone.ackA
local ackSnd = theZone.ackASnd
local meth = theZone.outMethA -- currently not used
-- decode A..X
if theItemIndex == "B"then
@ -740,6 +747,7 @@ function radioMenu.doMenuX(args)
outVal = theZone.outValB
ack = theZone.ackB
ackSnd = theZone.ackBSnd
meth = theZone.outMethB
elseif theItemIndex == "C" then
cd = radioMenu.cdByGID(theZone.mcdC, theZone, theGroup) -- theZone.mcdC
busy = theZone.busyC
@ -747,6 +755,7 @@ function radioMenu.doMenuX(args)
outVal = theZone.outValC
ack = theZone.ackC
ackSnd = theZone.ackCSnd
meth = theZone.outMethC
elseif theItemIndex == "D" then
cd = radioMenu.cdByGID(theZone.mcdD, theZone, theGroup) -- theZone.mcdD
busy = theZone.busyD
@ -754,6 +763,7 @@ function radioMenu.doMenuX(args)
outVal = theZone.outValD
ack = theZone.ackD
ackSnd = theZone.ackDSnd
meth = theZone.outMethD
end
-- see if we are on cooldown
@ -793,6 +803,7 @@ function radioMenu.doMenuX(args)
-- poll flag, override with outVal if set
if outVal then
--outVal = "#"..outVal -- we force immediate mode
-- now replaced by 'valX' attribute
theZone:pollFlag(theFlag, outVal)
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: overriding index " .. theItemIndex .. " output method <" .. theZone.radioMethod .. "> with immediate value <" .. outVal .. ">", 30)

View File

@ -1,5 +1,5 @@
debugger = {}
debugger.version = "3.0.0"
debugger.version = "3.1.0"
debugDemon = {}
debugDemon.version = "2.1.0"
@ -45,7 +45,8 @@ debugger.log = ""
- x *f
- x *z
- x ?
3.1.0 - new DCS world events 57-61
- x ! xref hanging/unconnected
--]]--
@ -122,7 +123,11 @@ debugDemon.eventList = {
["54"] = "S_EVENT_MISSION_WINNER = 54",
["55"] = "S_EVENT_POSTPONED_TAKEOFF = 55",
["56"] = "S_EVENT_POSTPONED_LAND = 56",
["57"] = "S_EVENT_MAX = 57",
["57"] = "S_EVENT_SIMULATION_FREEZE = 57",
["58"] = "S_EVENT_SIMULATION_UNFREEZE = 58",
["59"] = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59",
["60"] = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60",
["61"] = "S_EVENT_MAX = 61",
}
debugger.spawnTypes = {
@ -150,7 +155,7 @@ debugger.spawnTypes = {
-- XREF MODULE
--
xref = {}
xref.version = "1.0.0"
xref.version = "1.1.0"
xref.dmlObjects = {} -- dict by zone name:upper()
-- has inputs: dict of string for each '?' input, contains input flag name
-- has output: dict of array for each output '!', contains output flag names as array
@ -158,7 +163,7 @@ xref.flags = {} -- dict by flag name
-- has froms: dict of zone of attributes that can write to this flags
-- has tos: dict of zones of attributes that read from flag
function xref.getDmlObject(name)
function xref.getDmlObject(name) -- lazy alloc
name = name:upper()
local theObject = xref.dmlObjects[name]
if not theObject then
@ -170,7 +175,7 @@ function xref.getDmlObject(name)
return theObject
end
function xref.getFlag(name, theZone)
function xref.getFlag(name, theZone) -- lazy alloc
if theZone and dcsCommon.stringStartsWith(name, "*") then
-- local name conversion
name = theZone.name .. name
@ -178,8 +183,8 @@ function xref.getFlag(name, theZone)
local theFlag = xref.flags[name]
if not theFlag then
theFlag = {}
theFlag.froms = {} -- dict by zone name/output that write to this flag
theFlag.tos = {} -- dict by zone name / inputs that reads from this flag
theFlag.froms = {} -- dict by zone name: OUTPUTS that write to this flag
theFlag.tos = {} -- dict by zone name: INPUTS that read from this flag
xref.flags[name] = theFlag
end
return theFlag
@ -236,20 +241,21 @@ function xref.scanMissionZones()
-- iterate all properties
local attributes = theZone:getAllZoneProperties()
for name, value in pairs(attributes) do
local n2 = dcsCommon.trim(name)
-- find inputs
if dcsCommon.stringEndsWith(name, "?") then
xref.addInput(theZone, name, value)
if dcsCommon.stringEndsWith(n2, "?") then
xref.addInput(theZone, n2, value)
end
-- find outputs
if dcsCommon.stringEndsWith(name, "!") then
xref.addOutput(theZone, name, value)
if dcsCommon.stringEndsWith(n2, "!") then
xref.addOutput(theZone, n2, value)
end
-- other stuff, e.g. "#"
-- find outputs
if dcsCommon.stringEndsWith(name, "#") then
xref.addOutput(theZone, name, value)
if dcsCommon.stringEndsWith(n2, "#") then
xref.addOutput(theZone, n2, value)
end
end
end
@ -289,8 +295,6 @@ function xref.xrefFlag(name)
end
end
end
-- trigger.action.outText(msg, 30)
return msg
end
@ -323,9 +327,7 @@ function xref.xrefZone(name, theObject)
end
end
-- trigger.action.outText(msg, 30)
return msg
return msg
end
function xref.xrefName(name)
@ -362,41 +364,93 @@ function xref.allZones()
end
function xref.xall()
msg = ""
local msg = ""
-- now dump all flags
for flagName, data in pairs (xref.flags) do
-- msg = msg .. xref.xrefFlag(flagName)
msg = msg .. xref.xrefName(flagName)
end
-- dump all zones
for zoneName, data in pairs(xref.dmlObjects) do
-- msg = msg .. xref.xrefZone(zoneName, data)
msg = msg .. xref.xrefName(zoneName)
end
return msg
-- trigger.action.outText(msg, 30)
end
function xref.unconnected()
local msg = "xref: unconnected flags/commands:\n"
local badFroms = {}
local badTos = {}
for name, theFlag in pairs(xref.flags) do
-- look for single-direction flags, i.e those that have
-- no froms or no tos.
if dcsCommon.getSizeOfTable(theFlag.froms) < 1 then
-- this flag has output referencing it, so the input hangs
badFroms[name] = theFlag
end
if dcsCommon.getSizeOfTable(theFlag.tos) < 1 then
-- this flag has output referencing it, so the input hangs
badTos[name] = theFlag
end
end
local hasUnconnected = false
if dcsCommon.getSizeOfTable(badFroms) > 0 then
msg = msg .. "<Flag/Command> used at [zone]:INPUT that listens for/to nothing:\n"
for name, theFlag in pairs(badFroms) do
msg = msg .. " <" .. name .. ">: "
local froms = theFlag.tos -- crossing from and to here!
for zName, outList in pairs(froms) do
msg = msg .. "[" .. zName .. "]:"
local c = 0
for idx, outName in pairs(outList) do
if c > 0 then msg = msg .. ", " end
c = 1
msg = msg .. outName
end
msg = msg .. "\n"
end
end
msg = msg .. " -- END OF LIST --\n\n"
hasUnconnected = true
end
if dcsCommon.getSizeOfTable(badTos) > 0 then
msg = msg .. "<Flag/Command> used at [zone]:OUTPUT that sends to nobody:\n"
for name, theFlag in pairs(badTos) do
msg = msg .. " <" .. name .. ">: "
local tos = theFlag.froms -- crossing!
for zName, inList in pairs(tos) do
msg = msg .. "[" .. zName .. "]:"
local c = 0
for idx, inName in pairs(inList) do
if c > 0 then msg = msg .. ", " end
c = 1
msg = msg .. inName
end
end
msg = msg .. "\n"
end
hasUnconnected = true
msg = msg .. " -- END OF LIST --\n\n"
end
if not hasUnconnected then
msg = msg .. "\n -- NONE --\n"
end
return msg
end
function xref.start()
xref.scanMissionZones()
local flagNum = dcsCommon.getSizeOfTable(xref.flags)
local dmlObNum = dcsCommon.getSizeOfTable(xref.dmlObjects)
trigger.action.outText("XRef v" .. xref.version .. " full DML object scan on mission complete:\n<" .. flagNum .. "> flags are referenced in <" .. dmlObNum .. "> DML zones", 30)
-- trigger.action.outText(xref.xall(), 30)
trigger.action.outText("XRef v" .. xref.version .. " full DML object scan on mission complete:\n<" .. flagNum .. "> flags are referenced in <" .. dmlObNum .. "> DML zones", 30)
end
-- run the xref
xref.start()
--[[--
to do
scan messenger and wildcards for flag access
--]]--
--
-- DEBUGGER MAIn
--
@ -436,7 +490,6 @@ function debugger.saveLog(name)
end
end
--
-- tracking flags
--
@ -764,7 +817,6 @@ function debugger.update()
end
end
--
-- Config & Start
--
@ -950,7 +1002,6 @@ function debugDemon:onEvent(theEvent)
end
if theEvent.id == world.event.S_EVENT_MARK_CHANGE then
-- trigger.action.outText("debugger: Mark Change event received", 30)
-- when changed, the mark's text is examined for a command
-- if it starts with the 'mark' string ("-" by default) it is processed
-- by the command processor
@ -958,42 +1009,26 @@ function debugDemon:onEvent(theEvent)
-- else an error is displayed and the mark remains.
if debugDemon.hasMark(theEvent.text) then
-- strip the mark
local cCommand = dcsCommon.clone(theEvent.text, true)
local cCommand = dcsCommon.clone(theEvent.text)
local commandString = cCommand:sub(1+debugDemon.markOfDemon:len())
-- break remainder apart into <command> <arg1> ... <argn>
local commands = dcsCommon.splitString(commandString, debugDemon.splitDelimiter)
-- this is a command. process it and then remove it if it was executed successfully
local cTheEvent = dcsCommon.clone(theEvent, true) -- strip meta tables
local cTheEvent = dcsCommon.clone(theEvent) -- strip meta tables
local args = {commands, cTheEvent}
-- defer execution for 0.1s to get out of trx bracked
timer.scheduleFunction(debugDemon.deferredDebug, args, timer.getTime() + 0.1)
debugDemon.activeIdx = cTheEvent.idx
--[[--
local success = debugDemon.executeCommand(commands, cTheEvent) -- execute on a clone, not original
-- remove this mark after successful execution
if success then
trigger.action.removeMark(theEvent.idx)
else
-- we could play some error sound
end
--]]--
end
end
if theEvent.id == world.event.S_EVENT_MARK_REMOVED then
-- trigger.action.outText("Mark Remove received, removing idx <" .. theEvent.idx .. ">.", 30)
debugDemon.activeIdx = nil
end
end
function debugDemon.deferredDebug(args)
-- trigger.action.outText("enter deferred debug command", 30)
-- if not debugDemon.activeIdx then
-- trigger.action.outText("Debugger: window was closed, debug command ignored.", 30)
-- return
-- end
local commands = args[1]
local cTheEvent = args[2]
local success = debugDemon.executeCommand(commands, cTheEvent) -- execute on a clone, not original
@ -1884,8 +1919,11 @@ function debugDemon.processXrefCommand(args, event)
if not larg or larg == "" then larg = "?" end
larg = larg:lower()
if larg == "?" then
debugger.outText("*** xRef: ? = help (this), * = xref all, *f = list all DML flags, *z = list all DML zones, <name> xref flag or zone", 30)
debugger.outText("*** xRef: ? = help (this), ! = xref all 'hanging' or unconnected flags/commands, * = xref all, *f = list all DML flags, *z = list all DML zones, <name> xref flag or zone", 30)
return true -- leave up
elseif larg == "!" then
debugger.outText(xref.unconnected(), 30)
return true
elseif larg == "*" then
debugger.outText(xref.xall(), 30)
return true

Binary file not shown.

Binary file not shown.