Version 2.0.1

Updates to Debugger, csarManager, PlayerScore/objectDestructDetector
This commit is contained in:
Christian Franz 2024-01-25 08:12:29 +01:00
parent c9694c3176
commit 38d6487de7
23 changed files with 565 additions and 455 deletions

Binary file not shown.

Binary file not shown.

View File

@ -12,27 +12,7 @@ rndFlags.requiredLibs = {
Copyright 2022 by Christian Franz and cf/x Copyright 2022 by Christian Franz and cf/x
Version History Version History
1.0.0 - Initial Version
1.1.0 - DML flag conversion:
flagArrayFromString: strings OK, trim
remove pollFlag
pollFlag from cfxZones, include zone
randomBetween for pollSize
pollFlag to bang done with inc
getFlagValue in update
some code clean-up
rndMethod synonym
1.2.0 - Watchflag integration
1.3.0 - DML simplification: RND!
zone-local verbosity
1.3.1 - 'done+1' --> 'done!', using rndMethod instead of 'inc'
- added zonal verbosity
- added 'rndDone!' flag
- rndMethod defaults to "inc"
1.3.2 - moved flagArrayFromString to dcsCommon
- minor clean-up
1.4.0 - persistence
1.4.1 - a little less verbosity
2.0.0 - dmlZones, OOP 2.0.0 - dmlZones, OOP
--]] --]]

View File

@ -158,3 +158,13 @@ if not cfxSSBSingleUse.start()then
trigger.action.outText("SSB Single Use failed to start up.", 30) trigger.action.outText("SSB Single Use failed to start up.", 30)
cfxSSBSingleUse = nil cfxSSBSingleUse = nil
end end
--[[--
limited use rebuild
have planes inside a zone to regulate:
numUp attribute for number of lives. defaults to one
reinforce? to make availabe again, resets numLeft to numUp - warning: check with other inhibitors
onStart = no will inhibit at start
exempt - limits to not apply to planes in this zone. same as -1 for numUp
--]]--

View File

@ -1,5 +1,5 @@
tdz = {} tdz = {}
tdz.version = "1.0.2" tdz.version = "1.0.3"
tdz.requiredLibs = { tdz.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -17,6 +17,7 @@ VERSION HISTORY
helo attribute helo attribute
1.0.2 - manual placement option 1.0.2 - manual placement option
filters FARPs filters FARPs
1.0.3 - "manual" now defaults to false
--]]-- --]]--
@ -89,7 +90,7 @@ function tdz.createTDZ(theZone)
local nearestRwy = nil local nearestRwy = nil
-- see if this is a manually placed runway -- see if this is a manually placed runway
if theZone:getBoolFromZoneProperty("manual", true) then if theZone:getBoolFromZoneProperty("manual", false) then
-- construct runway from trigger zone attributes -- construct runway from trigger zone attributes
if theZone.verbose or tdz.verbose then if theZone.verbose or tdz.verbose then
trigger.action.outText("+++TDZ: runway for <" .. theZone.name .. "> is manually placed", 30) trigger.action.outText("+++TDZ: runway for <" .. theZone.name .. "> is manually placed", 30)

View File

@ -1,34 +1,26 @@
airfield = {} airfield = {}
airfield.version = "2.0.0" airfield.version = "2.1.1"
airfield.requiredLibs = { airfield.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
} }
airfield.myAirfields = {} -- indexed by name airfield.myAirfields = {} -- indexed by af name, zone that links to it
airfield.gracePeriod = 3 airfield.gracePeriod = 3
airfield.allAirfields = {} -- inexed by name airfield.allAirfields = {} -- inexed by af name, db entries: base, cat
--[[-- --[[--
This module generates signals when the nearest airfield changes hands, This module generates signals when the nearest airfield changes hands,
can force the coalition of an airfield, and always provides the can force the coalition of an airfield, and always provides the
current owner as a value current owner as a value
Version History Version History^
1.0.0 - initial release
1.1.0 - added 'fixed' attribute
- added 'farps' attribute to individual zones
- allow zone.local farps designation
- always checks farp cap events
- added verbosity
1.1.1 - GC grace period correction of ownership
1.1.2 - 'show' attribute
line color attributes per zone
line color defaults in config
2.0.0 - show all airfields option 2.0.0 - show all airfields option
- fully reworked show options - fully reworked show options
- unmanaged airfields are automatically updated - unmanaged airfields are automatically updated
- full color support - full color support
-- support for FARPS as well -- support for FARPS as well
2.1.0 - added support for makeNeutral?
2.1.1 - bug fixing for DCS 2.9x airfield retrofit
--]]-- --]]--
@ -39,7 +31,9 @@ function airfield.collectAll()
local dropped = 0 local dropped = 0
for idx, aBase in pairs(allBases) do for idx, aBase in pairs(allBases) do
local entry = {} local entry = {}
local cat = Airbase.getCategory(aBase) -- DCS 2.9 hardened --local cat = Airbase.getCategory(aBase) -- DCS 2.9 hardened
-- ho! dcs 2.9.x retrofit screwed with Airfield.getCategory.
local cat = aBase:getDesc().category
-- cats: 0 = airfield, 1 = farp, 2 = ship -- cats: 0 = airfield, 1 = farp, 2 = ship
if (cat == 0) or (cat == 1) then if (cat == 0) or (cat == 1) then
local name = aBase:getName() local name = aBase:getName()
@ -50,6 +44,7 @@ function airfield.collectAll()
count = count + 1 count = count + 1
else else
dropped = dropped + 1 dropped = dropped + 1
-- trigger.action.outText("***dropped airbase <" .. aBase:getName() .. ">, cat = <" .. cat .. ">", 30)
end end
end end
if airfield.verbose then if airfield.verbose then
@ -98,6 +93,11 @@ function airfield.createAirFieldFromZone(theZone)
theZone.lastMakeBlue = trigger.misc.getUserFlag(theZone.makeBlue) theZone.lastMakeBlue = trigger.misc.getUserFlag(theZone.makeBlue)
end end
if theZone:hasProperty("makeNeutral?") then
theZone.makeNeutral = theZone:getStringFromZoneProperty("makeNeutral?", "<none>")
theZone.lastMakeNeutral = trigger.misc.getUserFlag(theZone.makeNeutral)
end
if theZone:hasProperty("autoCap?") then if theZone:hasProperty("autoCap?") then
theZone.autoCap = theZone:getStringFromZoneProperty("autoCap?", "<none>") theZone.autoCap = theZone:getStringFromZoneProperty("autoCap?", "<none>")
theZone.lastAutoCap = trigger.misc.getUserFlag(theZone.autoCap) theZone.lastAutoCap = trigger.misc.getUserFlag(theZone.autoCap)
@ -146,8 +146,11 @@ function airfield.createAirFieldFromZone(theZone)
-- now mark this zone as handled -- now mark this zone as handled
local entry = airfield.allAirfields[theZone.afName] local entry = airfield.allAirfields[theZone.afName]
if not entry then
trigger.action.outText("+++airF: WARNING - unlinked airfield <" .. theZone.afName .. "> for zone <" .. theZone.name .. ">", 30)
else
entry.linkedTo = theZone -- only remember last, but that's enough. entry.linkedTo = theZone -- only remember last, but that's enough.
end
end end
function airfield.markAirfieldOnMap(theAirfield, lineColor, fillColor) function airfield.markAirfieldOnMap(theAirfield, lineColor, fillColor)
@ -246,6 +249,10 @@ function airfield.airfieldCaptured(theBase)
airfield.untendedCapture(bName, theBase) airfield.untendedCapture(bName, theBase)
return return
end -- not attached to a zone end -- not attached to a zone
if theZone.verbose or airfield.verbose then
trigger.action.outText("+++airF: capturing <" .. bName .. "> for zone <" .. theZone.name .. ">", 30)
end
local newCoa = theBase:getCoalition() local newCoa = theBase:getCoalition()
theZone.owner = newCoa theZone.owner = newCoa
@ -324,6 +331,21 @@ function airfield.update()
end end
end end
if theZone.makeNeutral and theZone:testZoneFlag(theZone.makeNeutral, theZone.triggerMethod, "lastMakeNeutral") then
if theZone.verbose or airfield.verbose then
trigger.action.outText("+++airF: 'makeNeutral' triggered for airfield <" .. afName .. "> in zone <" .. theZone:getName() .. ">", 30)
end
if theAirfield:autoCaptureIsOn() then
-- turn off autoCap
airfield.assumeControl(theZone)
end
theAirfield:setCoalition(0) -- make it blue
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
end
if theZone.autoCap and theZone:testZoneFlag(theZone.autoCap, theZone.triggerMethod, "lastAutoCap") then if theZone.autoCap and theZone:testZoneFlag(theZone.autoCap, theZone.triggerMethod, "lastAutoCap") then
if theAirfield:autoCaptureIsOn() then if theAirfield:autoCaptureIsOn() then
-- do nothing -- do nothing

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.1.1" cfxZones.version = "4.1.2"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -43,6 +43,7 @@ cfxZones.version = "4.1.1"
- 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false) - 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false)
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well - 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates - 4.1.1 - evalRemainder() updates
- 4.1.2 - hash property missing warning
--]]-- --]]--
@ -2299,6 +2300,14 @@ function dmlZone:hasProperty(theProperty)
return false return false
end end
if string.sub(theProperty, -1) == "#" then
local lessOp = theProperty:sub(1,-2)
if self:getZoneProperty(lessOp) ~= nil then
trigger.action.outText("*** NOTE: " .. self.name .. "'s property <" .. lessOp .. "> may be missing a hash mark ('#') at end", 30)
end
return false
end
return false return false
end end
return true return true

View File

@ -1,37 +1,6 @@
civAir = {} civAir = {}
civAir.version = "3.0.1" civAir.version = "3.0.2"
--[[-- --[[--
1.0.0 initial version
1.1.0 exclude list for airfields
1.1.1 bug fixes with remove flight
and betweenHubs
check if slot is really free before spawning
add overhead waypoint
1.1.2 inAir start possible
1.2.0 civAir can use own config file
1.2.1 slight update to config file (moved active/idle)
1.3.0 add ability to use zones to add closest airfield to
trafficCenters or excludeAirfields
1.4.0 ability to load config from zone to override
all configs it finds
module check
removed obsolete civAirConfig module
1.5.0 process zones as in all other modules
verbose is part of config zone
reading type array from config corrected
massive simplifications: always between zoned airfieds
exclude list and include list
1.5.1 added depart only and arrive only options for airfields
1.5.2 fixed bugs inb verbosity
2.0.0 dmlZones
inbound zones
outbound zones
on start location is randomizes 30-70% of the way
guarded 'no longer exist' warning for verbosity
changed unit naming from -civA to -GA
strenghtened guard on testing against free slots for other units
flights are now of random neutral countries
maxFlights synonym for maxTraffic
3.0.0 liveries support 3.0.0 liveries support
default liveries for Yak-50 (main test case) default liveries for Yak-50 (main test case)
default liveries for C-130, c-17A, IL-76MD, An-30M, An-26B default liveries for C-130, c-17A, IL-76MD, An-30M, An-26B
@ -45,6 +14,7 @@ civAir.version = "3.0.1"
3.0.1 protest option, on by default 3.0.1 protest option, on by default
protest action protest action
spawning now works correctly for groupType spawning now works correctly for groupType
3.0.2 clean-up
--]]-- --]]--

View File

@ -259,6 +259,14 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
trigger.action.outText("+++clnZ: masterOwner for <" .. theZone.name .. "> set successfully to to itself, currently owned by faction <" .. theZone.owner .. ">", 30) trigger.action.outText("+++clnZ: masterOwner for <" .. theZone.name .. "> set successfully to to itself, currently owned by faction <" .. theZone.owner .. ">", 30)
end end
end end
if theZone.verbose or cloneZones.verbose then
trigger.action.outText("+++clnZ: ownership of <" .. theZone.name .. "> tied to zone <" .. theZone.masterOwner .. ">", 30)
end
-- check that the zone exists in DCS
local theMaster = cfxZones.getZoneByName(theZone.masterOwner)
if not theMaster then
trigger.action.outText("clnZ: WARNING: cloner's <" .. theZone.name .. "> master owner named <" .. theZone.masterOwner .. "> does not exist!", 30)
end
end end
theZone.turn = theZone:getNumberFromZoneProperty("turn", 0) theZone.turn = theZone:getNumberFromZoneProperty("turn", 0)

View File

@ -1,70 +1,9 @@
csarManager = {} csarManager = {}
csarManager.version = "2.3.2" csarManager.version = "3.0.0"
csarManager.verbose = false
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 1.0.0 initial version
- 1.0.1 - smoke optional
- airframeCrashed method for airframe manager
- removed '(downed )' when re-picked up
- fixed oclock
- 1.0.2 - hover retrieval
- 1.0.3 - corrected a bug in oclock during hovering
- 1.0.4 - now correctly allocates pilot to coalition via dcscommon.coalition2county
- 1.1.0 - pilot adds weight to unit
- module check
- 2.0.0 - weight managed via cargoSuper
- 2.0.1 - getCSARBaseforZone()
- check if zone landed in has owner attribute
to provide compatibility with owned zones,
FARPZones etc that keep zone.owner up to date
- 2.0.2 - use parametric csarManager.hoverAlt
- use hoverDuration
- 2.0.3 - corrected bug in hoverDuration
- 2.0.4 - guard in createCSARMission for cfxCommander
- 2.1.0 - startCSAR?
- deferrable missions
- verbose
- ups
- useSmoke
- smokeColor
- reworked smoking the loc
- config zone
- csarRedDelivered
- csarBlueDelivered
- finally fixed smoke performance bug
- csarManager.vectoring optional
- 2.1.1 - zone-local verbosity
- 2.1.2 - 'downed' machinations (paranthese)S
- verbosity
- 2.1.3 - theMassObject now local
- winch pickup now also adds weight so they can be returned
- made some improvements to performance by making vars local
- 2.2.0 - interface for autoCSAR
createDownedPilot() - added existingUnit option
createCSARMissionData() - added existinUnit option
- when no config zone, runs through empty zone
- actionSound
- integration with playerScore
- score global and per-mission
- isCSARTarget API
- 2.2.1 - added troopCarriers attribute to config
- passes own troop carriers to dcsCommon.isTroopCarrier()
- 2.2.2 - enable CSAR missions in water
- csar name defaults to zone name
- better randomization of pilot's point in csar mission,
supports quad zone
- 2.2.3 - better support for red/blue
- allow neutral pick-up
- directions to closest safe zone
- CSARBASE attribute now carries coalition
- deprecated coalition attribute
- 2.2.4 - CSAR attribute value defaults name
- start? attribute for CSAR as startCSAR? synonym
- 2.2.5 - manual freq for CSAR was off by a factor of 10 - Corrected
- 2.2.6 - useFlare, now also launches a flare in addition to smoke
- zone testing uses getPoint for zones, supports moving csar zones
- 2.3.0 - dmlZones - 2.3.0 - dmlZones
- onRoad attribute for CSAR mission Zones - onRoad attribute for CSAR mission Zones
- rndLoc support - rndLoc support
@ -74,32 +13,27 @@ csarManager.ups = 1
- offset zone on randomized soldier - offset zone on randomized soldier
- smokeDist - smokeDist
- 2.3.2 - DCS 2.9 getCategory() fix - 2.3.2 - DCS 2.9 getCategory() fix
- 3.0.0 - moved mission creation out of update loop into own
- removed cfxPlayer dependency
- new event manager
- no longer single-proccing pilots
- can also handle aircraft - isTroopCarrier
INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED
INTEGRATES WITH LIMITE AIRFRAMES IF INSTALLED
--]]-- --]]--
-- modules that need to be loaded BEFORE I run -- modules that need to be loaded BEFORE I run
csarManager.requiredLibs = { csarManager.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
"cfxZones", -- zones management for CSAR and CSAR Mission zones "cfxZones", -- zones management for CSAR and CSAR Mission zones
"cfxPlayer", -- player monitoring and group monitoring
"nameStats", -- generic data module for weight "nameStats", -- generic data module for weight
"cargoSuper", "cargoSuper",
-- "cfxCommander", -- needed only if you want to hand-create CSAR missions
} }
--
-- OPTIONS
--
csarManager.useSmoke = true
csarManager.smokeColor = 4 -- when using smoke
-- unitConfigs contain the config data for any helicopter -- unitConfigs contain the config data for any helicopter
-- currently in the game. The Array is indexed by unit name -- currently in the game. The Array is indexed by unit name
csarManager.unitConfigs = {} csarManager.unitConfigs = {}
csarManager.myEvents = {3, 4, 5} -- 3 = take off, 4 = land, 5 = crash
-- --
-- CASR MISSION -- CASR MISSION
@ -157,15 +91,10 @@ function csarManager.createDownedPilot(theMission, existingUnit)
aLocation.z, aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards -aHeading + 1.5) -- + 1.5 to turn inwards
-- WARNING: local theSideCJTF = dcsCommon.getACountryForCoalition(theMission.side)
-- coalition.addGroup takes the COUNTRY of the group, and derives the
-- coalition from that. So if mission.sie is 0, we use UN, if it is 1 (red) it
-- is joint red, if 2 it is joint blue
local theSideCJTF = dcsCommon.coalition2county(theMission.side) -- get the correct county CJTF
theMission.group = coalition.addGroup(theSideCJTF, theMission.group = coalition.addGroup(theSideCJTF,
Group.Category.GROUND, Group.Category.GROUND,
theBoyGroup) theBoyGroup)
if theBoyGroup then if theBoyGroup then
else else
@ -176,10 +105,10 @@ function csarManager.createDownedPilot(theMission, existingUnit)
end end
-- we now use commands to send radio transmissions -- we now use commands to send radio transmissions
local ADF = 20 + math.random(90) local ADF = 20 + math.random(150) -- create a random number between 20 and 110 --> 200'000 .. 1'700'000 KHz = 200KHz .. 1'700 KHz
if theMission.freq then ADF = theMission.freq else theMission.freq = ADF end if theMission.freq then ADF = theMission.freq else theMission.freq = ADF end
local theCommands = cfxCommander.createCommandDataTableFor(theMission.group) local theCommands = cfxCommander.createCommandDataTableFor(theMission.group)
local cmd = cfxCommander.createSetFrequencyCommand(ADF) -- freq in 10000 Hz local cmd = cfxCommander.createSetFrequencyCommand(ADF) -- freq in 10'000 Hz
cfxCommander.addCommand(theCommands, cmd) cfxCommander.addCommand(theCommands, cmd)
cmd = cfxCommander.createTransmissionCommand(csarManager.beaconSound) cmd = cfxCommander.createTransmissionCommand(csarManager.beaconSound)
cfxCommander.addCommand(theCommands, cmd) cfxCommander.addCommand(theCommands, cmd)
@ -259,7 +188,7 @@ end
-- UNIT CONFIG -- UNIT CONFIG
-- --
function csarManager.resetConfig(conf) function csarManager.resetConfig(conf)
-- reset only ovberwrites mission-relevant data -- reset only overwrites mission-relevant data
conf.troopsOnBoard = {} -- number of rescued missions conf.troopsOnBoard = {} -- number of rescued missions
local myName = conf.name local myName = conf.name
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
@ -309,47 +238,17 @@ end
-- --
-- E V E N T H A N D L I N G -- E V E N T H A N D L I N G
-- --
function csarManager.isInteresting(eventID) function csarManager:onEvent(event)
-- return true if we are interested in this event, false else
for key, evType in pairs(csarManager.myEvents) do
if evType == eventID then return true end
end
return false
end
function csarManager.preProcessor(event)
-- make sure it has an initiator -- make sure it has an initiator
if not event.initiator then return false end -- no initiator if not event.initiator then return end
local theUnit = event.initiator local theUnit = event.initiator
if not theUnit.getDesc then return fase end -- not a unit
local cat = theUnit:getDesc().category --theUnit:getCategory()
if cat ~= 1 then -- Unit.Category.HELICOPTER
return false
end
--trigger.action.outText("+++csar: event " .. event.id .. " for cat = " .. cat .. " (helicopter?) unit " .. theUnit:getName(), 30) if not dcsCommon.isPlayerUnit(theUnit) then return end -- not a player unit
if not cfxPlayer.isPlayerUnit(theUnit) then -- only proceed if troop carrier (no more helo checks, all troop carriers, so osprey and harrier can be used if so desired)
--trigger.action.outText("+++csar: rejected event: " .. theUnit:getName() .. " not a player helo", 30) if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
return false
end -- not a player unit
return csarManager.isInteresting(event.id)
end
function csarManager.postProcessor(event)
-- don't do anything for now
end
function csarManager.somethingHappened(event)
-- when this is invoked, the preprocessor guarantees that
-- it's an interesting event
-- unit is valid and player
-- airframe category is helicopter
local theUnit = event.initiator
local ID = event.id local ID = event.id
local myType = theUnit:getTypeName()
if ID == 4 then -- landed if ID == 4 then -- landed
csarManager.heloLanded(theUnit) csarManager.heloLanded(theUnit)
end end
@ -362,15 +261,16 @@ function csarManager.somethingHappened(event)
csarManager.heloCrashed(theUnit) csarManager.heloCrashed(theUnit)
end end
if ID == 15 then -- player helicopter birth
-- we need to set up comms for this unit
csarManager.setCommsMenu(theUnit) csarManager.setCommsMenu(theUnit)
end
end end
--
-- --
-- CSAR LANDED -- CSAR LANDED
-- --
--
function csarManager.successMission(who, where, theMission) function csarManager.successMission(who, where, theMission)
-- who is -- who is
-- where is -- where is
@ -397,7 +297,6 @@ function csarManager.successMission(who, where, theMission)
-- now call callback for coalition side -- now call callback for coalition side
-- callback has format callback(coalition, success true/false, numberSaved, descriptionText) -- callback has format callback(coalition, success true/false, numberSaved, descriptionText)
csarManager.invokeCallbacks(theMission.side, true, 1, "success") csarManager.invokeCallbacks(theMission.side, true, 1, "success")
trigger.action.outSoundForCoalition(theMission.side, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForCoalition(theMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
@ -491,13 +390,11 @@ function csarManager.heloLanded(theUnit)
conf.troopsOnBoard = {} -- empty out troops on board conf.troopsOnBoard = {} -- empty out troops on board
-- we do *not* return so we can pick up troops on -- we do *not* return so we can pick up troops on
-- a CSARBASE if they were dropped there -- a CSARBASE if they were dropped there
else else
if csarManager.verbose or base.zone.verbose then if csarManager.verbose or base.zone.verbose then
trigger.action.outText("+++csar: touchdown of <" .. myName .. "> occured outside of csar zone <" .. base.zone.name .. ">", 30) trigger.action.outText("+++csar: touchdown of <" .. myName .. "> occured outside of csar zone <" .. base.zone.name .. ">", 30)
end end
end end
else -- not on my side else -- not on my side
if csarManager.verbose or base.zone.verbose then if csarManager.verbose or base.zone.verbose then
trigger.action.outText("+++csar: base <" .. base.zone.name .. "> is on side <" .. currentBaseSide .. ">, which is not on my side <" .. mySide .. ">.", 30) trigger.action.outText("+++csar: base <" .. base.zone.name .. "> is on side <" .. currentBaseSide .. ">, which is not on my side <" .. mySide .. ">.", 30)
@ -930,46 +827,6 @@ function csarManager.directions(args)
trigger.action.outTextForGroup(conf.id, report, 30) trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end end
--
-- Player event callbacks
--
function csarManager.playerChangeEvent(evType, description, player, data)
if evType == "newGroup" then
local theUnit = data.primeUnit
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
csarManager.setCommsMenu(theUnit) -- allocates new config
-- trigger.action.outText("+++csar: added " .. theUnit:getName() .. " to comms menu", 30)
return
end
if evType == "removeGroup" then
-- trigger.action.outText("+++csar: a group disappeared", 30)
local conf = csarManager.getConfigForUnitNamed(data.primeUnitName)
if conf then
csarManager.removeCommsFromConfig(conf)
end
return
end
if evType == "leave" then
local conf = csarManager.getConfigForUnitNamed(player.unitName)
if conf then
csarManager.resetConfig(conf)
end
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,
-- except we need to reset the conf so no troops are carried any longer
local conf = csarManager.getConfigForUnitNamed(data.oldUnitName)
if conf then
csarManager.resetConfig(conf)
end
end
end
-- --
-- CSAR Bases -- CSAR Bases
@ -1056,6 +913,10 @@ function csarManager.launchFlare(args)
trigger.action.signalFlare(loc, color, 0) trigger.action.signalFlare(loc, color, 0)
end end
-- WE ASSUME MISSIONS AREN'T TOO CLOSE TOGETHER TO
-- MESS UP MESSAGING OR PICKUP
-- if they are less than 2d apart, they can crosstalk each other
function csarManager.update() -- every second function csarManager.update() -- every second
-- schedule next invocation -- schedule next invocation
timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups) timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups)
@ -1065,20 +926,22 @@ function csarManager.update() -- every second
-- now scan through all helo groups and see if they are close to a -- now scan through all helo groups and see if they are close to a
-- CSAR zone and initiate the help sequence -- CSAR zone and initiate the help sequence
local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it! -- local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
-- contains per group a player record, use prime unit to access player's unit local allPlayerUnits = dcsCommon.getAllExistingPlayersAndUnits() -- indexed by player name
for gname, pgroup in pairs(allPlayerGroups) do --old -- contains per group a player record, use prime unit to access player's unit
local aUnit = pgroup.primeUnit -- get prime unit of that group for pname, aUnit in pairs(allPlayerUnits) do
if aUnit:isExist() and aUnit:inAir() then -- exists and is flying if --aUnit:isExist() and
aUnit:inAir() and
dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers)
then -- troop carrier and is flying
local uPoint = aUnit:getPoint() local uPoint = aUnit:getPoint()
local uName = aUnit:getName() local uName = aUnit:getName()
local uGroup = aUnit:getGroup() local uGroup = aUnit:getGroup()
local uID = uGroup:getID() local uID = uGroup:getID()
local uSide = aUnit:getCoalition() local uSide = aUnit:getCoalition()
local agl = dcsCommon.getUnitAGL(aUnit) local agl = dcsCommon.getUnitAGL(aUnit)
if dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers) then local needsGC = false
-- scan through all available csar missions to see if we are close -- local hasMessaged = false
-- enough to trigger comms
for idx, csarMission in pairs (csarManager.openMissions) do for idx, csarMission in pairs (csarManager.openMissions) do
-- check if we are inside trigger range on the same side -- check if we are inside trigger range on the same side
local mp = cfxZones.getPoint(csarMission.zone, true) local mp = cfxZones.getPoint(csarMission.zone, true)
@ -1086,7 +949,7 @@ function csarManager.update() -- every second
if ((uSide == csarMission.side) or (csarMission.side == 0) ) if ((uSide == csarMission.side) or (csarMission.side == 0) )
and (d < csarManager.rescueTriggerRange) then and (d < csarManager.rescueTriggerRange) then
-- we are in trigger distance. if we did not notify before -- we are in trigger distance. if we did not notify before
-- do it now -- do it now, we ever only do this once for a unit for any mission
if not dcsCommon.arrayContainsString(csarMission.messagedUnits, uName) then if not dcsCommon.arrayContainsString(csarMission.messagedUnits, uName) then
-- radio this unit with oclock and tell it they are in 2k range -- radio this unit with oclock and tell it they are in 2k range
-- also note if LZ is hot -- also note if LZ is hot
@ -1117,24 +980,25 @@ function csarManager.update() -- every second
trigger.action.outSoundForGroup(uID, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(uID, csarManager.actionSound) -- "Quest Snare 3.wav")
table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again
end end
-- also pop smoke if not popped already, or more than 5 minutes ago -- also pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
local smokePoint = dcsCommon.randomPointOnPerimeter( local smokePoint = dcsCommon.randomPointOnPerimeter(
csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z) --cfxZones.createHeightCorrectedPoint(csarMission.zone.point) csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z)
-- trigger.action.smoke(smokePoint, 4 )
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor) dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor)
csarMission.lastSmokeTime = timer.getTime() csarMission.lastSmokeTime = timer.getTime()
end end
-- now check if we are inside hover range and alt -- now check if we are inside hover range and alt
-- in order to simultate winch ops -- in order to simultate winch ops
-- WARNING: WE ALWAYS ONLY CHECK A SINGLE UNIT - the first alive -- if competition picked up, we skip this loop
local evacuee = csarMission.group:getUnit(1) local evacuee = nil
if csarMission.group then evacuee = csarMission.group:getUnit(1) end
if evacuee then if evacuee then
local ep = evacuee:getPoint() local ep = evacuee:getPoint()
d = dcsCommon.distFlat(uPoint, ep) d = dcsCommon.distFlat(uPoint, ep)
d = math.floor(d * 10) / 10 d = math.floor(d * 10) / 10
if d < csarManager.rescueTriggerRange * 0.5 then --csarManager.hoverRadius * 2 then if d < csarManager.rescueTriggerRange * 0.5 then
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit) local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock" local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock"
-- log distance -- log distance
@ -1156,10 +1020,11 @@ function csarManager.update() -- every second
-- we rescued the guy! -- we rescued the guy!
hoverMsg = "We have " .. csarMission.name .. " safely on board!" hoverMsg = "We have " .. csarMission.name .. " safely on board!"
local conf = csarManager.getUnitConfig(aUnit) local conf = csarManager.getUnitConfig(aUnit)
csarManager.removeMission(csarMission) -- mission now GC's after iteration csarManager.removeMission(csarMission)
table.insert(conf.troopsOnBoard, csarMission) table.insert(conf.troopsOnBoard, csarMission)
csarMission.group:destroy() -- will shut up radio as well csarMission.group:destroy() -- will shut up radio as well
csarMission.group = nil csarMission.group = nil -- no more evacuees
needsGC = true -- need filtering missions
-- now handle weight using cargoSuper -- now handle weight using cargoSuper
local theMassObject = cargoSuper.createMassObject( local theMassObject = cargoSuper.createMassObject(
@ -1178,13 +1043,13 @@ function csarManager.update() -- every second
trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30) trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30)
end end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) --trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, csarManager.actionSound) --"Quest Snare 3.wav") trigger.action.outSoundForGroup(uID, csarManager.actionSound)
return -- we only ever rescue one --return -- we only ever rescue one
end -- hovered long enough end -- hovered long enough
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) --trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op -- return -- only ever one winch op
else -- too high for hover else -- too high for hover
hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching" hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching"
csarMission.hoveringUnits[uName] = nil -- reset timer csarMission.hoveringUnits[uName] = nil -- reset timer
@ -1194,18 +1059,28 @@ function csarManager.update() -- every second
csarMission.hoveringUnits[uName] = nil csarMission.hoveringUnits[uName] = nil
end end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op --return -- only ever one winch op
else else
-- remove the hover indicator for this unit -- remove the hover indicator for this unit
csarMission.hoveringUnits[uName] = nil csarMission.hoveringUnits[uName] = nil
end -- inside 2 * hover dist? end -- inside 2 * hover dist?
else
end -- has evacuee -- somebody snatched the evacuee
end -- if has evacuee
end -- if in range end -- if in range
end -- for all missions end -- for all missions
end -- if troop carrier -- now GC all missions if we lifted a pilot up (we no longer return after first succesful)
end -- if exists if needsGC then
end -- for all players local filtered = {}
for idx, csarMission in pairs(csarManager.openMissions) do
if csarMission.group then
table.insert(filtered, csarMission)
end
end
csarManager.openMissions = filtered
end
end -- if in Air
end -- for all player units
-- now see and check if we need to spawn from a csar zone -- now see and check if we need to spawn from a csar zone
-- that has been told to spawn -- that has been told to spawn
@ -1216,6 +1091,18 @@ function csarManager.update() -- every second
-- local currVal = theZone:getFlagValue(theZone.startCSAR) -- local currVal = theZone:getFlagValue(theZone.startCSAR)
-- if currVal ~= theZone.lastCSARVal then -- if currVal ~= theZone.lastCSARVal then
if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission)
--theZone.lastCSARVal = currVal
if csarManager.verbose or theZone.verbose then
trigger.action.outText("+++csar: started CSAR mission for <" .. theZone.csarName .. ">", 30)
end
end
end
end
end
function csarManager.createCSARMissionFromZone(theZone)
-- set up random point in zone -- set up random point in zone
local mPoint = theZone:getPoint() local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
@ -1232,14 +1119,7 @@ function csarManager.update() -- every second
theZone.csarMapMarker, -- mapMarker theZone.csarMapMarker, -- mapMarker
0.1, --theZone.radius) -- radius 0.1, --theZone.radius) -- radius
nil) -- parashoo unit nil) -- parashoo unit
csarManager.addMission(theMission) return theMission
--theZone.lastCSARVal = currVal
if csarManager.verbose then
trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30)
end
end
end
end
end end
-- --
@ -1412,9 +1292,6 @@ function csarManager.readConfigZone()
csarManager.name = "csarManagerConfig" -- compat with cfxZones csarManager.name = "csarManagerConfig" -- compat with cfxZones
local theZone = cfxZones.getZoneByName("csarManagerConfig") local theZone = cfxZones.getZoneByName("csarManagerConfig")
if not theZone then if not theZone then
if csarManager.verbose then
trigger.action.outText("+++csar: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("csarManagerConfig") theZone = cfxZones.createSimpleZone("csarManagerConfig")
end end
csarManager.configZone = theZone -- save for flag banging compatibility csarManager.configZone = theZone -- save for flag banging compatibility
@ -1487,20 +1364,6 @@ function csarManager.start()
-- read config -- read config
csarManager.readConfigZone() csarManager.readConfigZone()
-- install callbacks for helo-relevant events
dcsCommon.addEventHandler(csarManager.somethingHappened, csarManager.preProcessor, csarManager.postProcessor)
-- now iterate through all player groups and install the CSAR Menu
local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
-- contains per group a player record, use prime unit to access player's unit
for gname, pgroup in pairs(allPlayerGroups) do
local aUnit = pgroup.primeUnit -- get prime unit of that group
csarManager.setCommsMenu(aUnit)
end
-- now install the new group notifier for new groups so we can remove and add CSAR menus
cfxPlayer.addMonitor(csarManager.playerChangeEvent)
-- now scan all zones that are CSAR drop-off for quick access -- now scan all zones that are CSAR drop-off for quick access
csarManager.processCSARBASE() csarManager.processCSARBASE()
@ -1508,17 +1371,27 @@ function csarManager.start()
-- and populate the available mission. -- and populate the available mission.
csarManager.processCSARZones() csarManager.processCSARZones()
-- now call update so we can monitor progress of all helos, and alert them -- install callbacks for helo-relevant events
-- when they are close to a CSAR --dcsCommon.addEventHandler(csarManager.somethingHappened, csarManager.preProcessor, csarManager.postProcessor)
world.addEventHandler(csarManager)
-- now iterate through all player groups and install the CSAR Menu
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for pName, aUnit in pairs(allPlayerUnits) do
csarManager.setCommsMenu(aUnit)
end
-- start updating and track all helicopters in the air against missions
csarManager.update() csarManager.update()
-- say hi! -- say hi!
trigger.action.outText("cf/x CSAR v" .. csarManager.version .. " started", 30) trigger.action.outText("cf/x CSAR Manager v" .. csarManager.version .. " started", 30)
return true return true
end end
-- let's get rolling -- let's get rolling
if not csarManager.start() then if not csarManager.start() then
trigger.action.outText("cf/x CSAR Manager v" .. csarManager.version .. " FAILED to run", 30)
csarManager = nil csarManager = nil
end end
@ -1540,5 +1413,6 @@ end
-- allow any airfied to be csarsafe by default, no longer *requires* csarbase -- allow any airfied to be csarsafe by default, no longer *requires* csarbase
-- remove cfxPlayer dependency -- minFreq, maxFreq settings for config and mission-individual
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.0.0" dcsCommon.version = "3.0.1"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option
@ -8,6 +8,7 @@ dcsCommon.version = "3.0.0"
- new pointInDirectionOfPointXYY() - new pointInDirectionOfPointXYY()
- createGroundGroupWithUnits now supports liveries - createGroundGroupWithUnits now supports liveries
- new getAllExistingPlayersAndUnits() - new getAllExistingPlayersAndUnits()
3.0.1 - clone: better handling of string type
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -1187,6 +1188,9 @@ dcsCommon.version = "3.0.0"
copy = tmp copy = tmp
end end
end end
elseif orig_type == "string" then
local tmp = ""
copy = tmp .. orig
else -- number, string, boolean, etc else -- number, string, boolean, etc
copy = orig copy = orig
end end

View File

@ -1,5 +1,5 @@
factoryZone = {} factoryZone = {}
factoryZone.version = "3.0.0" factoryZone.version = "3.1.0"
factoryZone.verbose = false factoryZone.verbose = false
factoryZone.name = "factoryZone" factoryZone.name = "factoryZone"
@ -11,6 +11,12 @@ factoryZone.name = "factoryZone"
- use maxRadius from zone for spawning to support quad zones - use maxRadius from zone for spawning to support quad zones
3.0.0 - support for liveries via "factoryLiveries" zone 3.0.0 - support for liveries via "factoryLiveries" zone
- OOP dmlZones - OOP dmlZones
3.1.0 - redD!, blueD!
- redP!, blueP!
- method
- productionTime config synonyme
- defendMe? attribute
- triggered 'shocked' mode via defendMe
--]]-- --]]--
factoryZone.requiredLibs = { factoryZone.requiredLibs = {
@ -104,16 +110,55 @@ function factoryZone.addFactoryZone(aZone)
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "factoryTriggerMethod", "change") aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "factoryTriggerMethod", "change")
end end
aZone.factoryMethod = aZone:getStringFromZoneProperty("factoryMethod", "inc")
if aZone:hasProperty("method") then
aZone.factoryMethod = aZone:getStringFromZoneProperty("method", "inc")
end
if aZone:hasProperty("redP!") then
aZone.redP = aZone:getStringFromZoneProperty("redP!", "none")
end
if aZone.redP and aZone.attackersRED ~= "none" then
trigger.action.outText("***WARNING: factory <" .. aZone.name .. "> has RED production and uses 'redP!'", 30)
end
if aZone:hasProperty("blueP!") then
aZone.blueP = aZone:getStringFromZoneProperty("blueP!", "none")
end
if aZone.blueP and aZone.attackersBLUE ~= "none" then
trigger.action.outText("***WARNING: factory <" .. aZone.name .. "> has BLUE production and uses 'blueP!'", 30)
end
if aZone:hasProperty("redD!") then
aZone.redD = aZone:getStringFromZoneProperty("redD!", "none")
end
if aZone.redD and aZone.defendersRED ~= "none" then
trigger.action.outText("***WARNING: factory <" .. aZone.name .. "> has RED defenders and uses 'redD!'", 30)
end
if aZone:hasProperty("blueD!") then
aZone.blueD = aZone:getStringFromZoneProperty("blueD!", "none")
end
if aZone.blueD and aZone.defendersBLUE ~= "none" then
trigger.action.outText("***WARNING: factory <" .. aZone.name .. "> has BLUE defenders and uses 'blueD!'", 30)
end
if aZone:hasProperty("defendMe?") then
aZone.defendMe = aZone:getStringFromZoneProperty("defendMe?", "none")
aZone.lastDefendMeValue = trigger.misc.getUserFlag(aZone.defendMe)
end
factoryZone.zones[aZone.name] = aZone factoryZone.zones[aZone.name] = aZone
factoryZone.verifyZone(aZone) factoryZone.verifyZone(aZone)
end end
function factoryZone.verifyZone(aZone) function factoryZone.verifyZone(aZone)
-- do some sanity checks -- do some sanity checks
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then -- if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
trigger.action.outText("+++factZ: " .. aZone.name .. " attackers need cfxGroundTroops to function", 30) -- now can also bang on flags, no more verification
end -- unless we want to beef them up
-- end
end end
function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation) function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
@ -183,6 +228,7 @@ end
-- --
function factoryZone.sendOutAttackers(aZone) function factoryZone.sendOutAttackers(aZone)
-- sanity check: never done for neutral zones -- sanity check: never done for neutral zones
if aZone.owner == 0 then if aZone.owner == 0 then
if aZone.verbose or factoryZone.verbose then if aZone.verbose or factoryZone.verbose then
@ -193,16 +239,31 @@ function factoryZone.sendOutAttackers(aZone)
-- only spawn if there are zones to attack -- only spawn if there are zones to attack
if not cfxOwnedZones.enemiesRemaining(aZone) then if not cfxOwnedZones.enemiesRemaining(aZone) then
if factoryZone.verbose then if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ - no enemies, resting ".. aZone.name, 30) trigger.action.outText("+++factZ - no enemies, resting ".. aZone.name, 30)
end end
return return
end end
if factoryZone.verbose then if factoryZone.verbose or aZone.verbose then
trigger.action.outText("+++factZ - attack cycle for ".. aZone.name, 30) trigger.action.outText("+++factZ - attack cycle for ".. aZone.name, 30)
end end
-- bang on xxxP!
if aZone.owner == 1 and aZone.redP then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling redP! <" .. aZone.redP .. "> for factrory <" .. aZone.name .. ">")
end
aZone:pollFlag(aZone.redP, aZone.factoryMethod)
end
if aZone.owner == 2 and aZone.blueP then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling blueP! <" .. aZone.blueP .. "> for factrory <" .. aZone.name .. ">")
end
aZone:pollFlag(aZone.blueP, aZone.factoryMethod)
end
-- step one: get the attackers -- step one: get the attackers
local attackers = aZone.attackersRED; local attackers = aZone.attackersRED;
if (aZone.owner == 2) then attackers = aZone.attackersBLUE end if (aZone.owner == 2) then attackers = aZone.attackersBLUE end
@ -248,8 +309,23 @@ function factoryZone.repairDefenders(aZone)
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
local unitTypes = {} -- build type names local unitTypes = {} -- build type names
-- if none, we are done -- if none, we are done, save for the outputs
if defenders == "none" then return end if (not defenders) or (defenders == "none") then
if aZone.owner == 1 and aZone.redD then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling redD! <" .. aZone.redD .. "> for repair factory <" .. aZone.name .. ">", 30)
end
aZone:pollFlag(aZone.redD, aZone.factoryMethod)
end
if aZone.owner == 2 and aZone.blueD then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling blueD! <" .. aZone.blueD .. "> for repair factory <" .. aZone.name .. ">", 30)
end
aZone:pollFlag(aZone.blueD, aZone.factoryMethod)
end
return
end
-- split theTypes into an array of types -- split theTypes into an array of types
allTypes = dcsCommon.trimArray( allTypes = dcsCommon.trimArray(
@ -308,6 +384,10 @@ end
function factoryZone.spawnDefenders(aZone) function factoryZone.spawnDefenders(aZone)
-- sanity check: never done for non-neutral zones -- sanity check: never done for non-neutral zones
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: starting defender cycle for <" .. aZone.name .. ">", 30)
end
if aZone.owner == 0 then if aZone.owner == 0 then
if aZone.verbose or factoryZone.verbose then if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: spawnDefenders invoked for NEUTRAL zone <" .. aZone.name .. ">", 30) trigger.action.outText("+++factZ: spawnDefenders invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
@ -315,7 +395,21 @@ function factoryZone.spawnDefenders(aZone)
return return
end end
-- bang! on xxxD!
local defenders = aZone.defendersRED; local defenders = aZone.defendersRED;
if aZone.owner == 1 and aZone.redD then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling redD! <" .. aZone.redD .. "> for factrory <" .. aZone.name .. ">", 30)
end
aZone:pollFlag(aZone.redD, aZone.factoryMethod)
end
if aZone.owner == 2 and aZone.blueD then
if aZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: polling blueD! <" .. aZone.blueD .. "> for factory <" .. aZone.name .. ">", 30)
end
aZone:pollFlag(aZone.blueD, aZone.factoryMethod)
end
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
-- before we spawn new defenders, remove the old ones -- before we spawn new defenders, remove the old ones
@ -388,7 +482,7 @@ function factoryZone.updateZoneProduction(aZone)
aZone.defenders then aZone.defenders then
-- we have defenders -- we have defenders
if aZone.defenders:isExist() then if aZone.defenders:isExist() then
-- isee if group was damaged -- see if group was damaged
if not aZone.lastDefenders then if not aZone.lastDefenders then
-- fresh group, probably from persistence, needs init -- fresh group, probably from persistence, needs init
aZone.lastDefenders = -1 aZone.lastDefenders = -1
@ -458,7 +552,7 @@ function factoryZone.updateZoneProduction(aZone)
if timer.getTime() > aZone.timeStamp + factoryZone.repairTime then if timer.getTime() > aZone.timeStamp + factoryZone.repairTime then
aZone.timeStamp = timer.getTime() aZone.timeStamp = timer.getTime()
-- wait's up, repair one defender, then check if full strength -- wait's up, repair one defender, then check if full strength
factoryZone.repairDefenders(aZone) factoryZone.repairDefenders(aZone) -- will also bang on redD and blueD if present
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit -- see if we are full strenght and if so go to attack, else set timer to reair the next unit
if aZone.defenders and aZone.defenders:isExist() and aZone.defenders:getSize() >= aZone.defenders:getInitialSize() then if aZone.defenders and aZone.defenders:isExist() and aZone.defenders:getSize() >= aZone.defenders:getInitialSize() then
-- we are at max size, time to produce some attackers -- we are at max size, time to produce some attackers
@ -468,6 +562,13 @@ function factoryZone.updateZoneProduction(aZone)
if factoryZone.verbose then if factoryZone.verbose then
trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30) trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
end end
elseif (aZone.redD or aZone.blueD) then
-- we start attacking cycle for out signal
nextState = "attacking"
aZone.timeStamp = timer.getTime()
if factoryZone.verbose then
trigger.action.outText("+++factZ: progessing tate " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name .. " for redD/blueD", 30)
end
end end
end end
@ -540,6 +641,19 @@ function factoryZone.update()
if theZone.activateFlag and cfxZones.testZoneFlag(theZone, theZone.activateFlag, theZone.factoryTriggerMethod, "lastActivateValue") then if theZone.activateFlag and cfxZones.testZoneFlag(theZone, theZone.activateFlag, theZone.factoryTriggerMethod, "lastActivateValue") then
theZone.paused = false theZone.paused = false
end end
-- see if zone external defendMe was polled to bring it to
-- shoked state
if theZone.defendMe and theZone:testZoneFlag(theZone.defendMe, theZone.factoryTriggerMethod, "lastDefendMeValue") then
if theZone.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: setting factory <" .. theZone.name .. "> to shocked/produce defender mode", 30)
end
theZone.state = "shocked"
theZone.timeStamp = timer.getTime()
theZone.lastDefenders = 0
theZone.defenders = nil -- nil, but no delete!
end
-- do production for this zone -- do production for this zone
factoryZone.updateZoneProduction(theZone) factoryZone.updateZoneProduction(theZone)
end -- iterating all zones end -- iterating all zones
@ -677,6 +791,9 @@ function factoryZone.readConfigZone(theZone)
factoryZone.verbose = theZone.verbose factoryZone.verbose = theZone.verbose
factoryZone.defendingTime = theZone:getNumberFromZoneProperty( "defendingTime", 100) factoryZone.defendingTime = theZone:getNumberFromZoneProperty( "defendingTime", 100)
factoryZone.attackingTime = theZone:getNumberFromZoneProperty( "attackingTime", 300) factoryZone.attackingTime = theZone:getNumberFromZoneProperty( "attackingTime", 300)
if theZone:hasProperty("productionTime") then
factoryZone.attackingTime = theZone:getNumberFromZoneProperty( "productionTime", 300)
end
factoryZone.shockTime = theZone:getNumberFromZoneProperty("shockTime", 200) factoryZone.shockTime = theZone:getNumberFromZoneProperty("shockTime", 200)
factoryZone.repairTime = theZone:getNumberFromZoneProperty( "repairTime", 200) factoryZone.repairTime = theZone:getNumberFromZoneProperty( "repairTime", 200)
factoryZone.targetZones = "OWNED" factoryZone.targetZones = "OWNED"

View File

@ -1,5 +1,5 @@
cfxObjectDestructDetector = {} cfxObjectDestructDetector = {}
cfxObjectDestructDetector.version = "2.0.0" cfxObjectDestructDetector.version = "2.0.2"
cfxObjectDestructDetector.verbose = false cfxObjectDestructDetector.verbose = false
cfxObjectDestructDetector.requiredLibs = { cfxObjectDestructDetector.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -18,6 +18,10 @@ cfxObjectDestructDetector.requiredLibs = {
ID changes (happens with map updates) ID changes (happens with map updates)
fail addZone when name property is missing fail addZone when name property is missing
2.0.1 check that the object is within the zone onEvent 2.0.1 check that the object is within the zone onEvent
2.0.2 redScore and bluescore attributes
API for PlayerScore to pass back redScore/blueScore
if objects was killed by player
verbosity bug fixed after kill (ref to old ID)
--]]-- --]]--
cfxObjectDestructDetector.objectZones = {} cfxObjectDestructDetector.objectZones = {}
@ -78,8 +82,59 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
elseif aZone:hasProperty("objectDestroyed!") then elseif aZone:hasProperty("objectDestroyed!") then
aZone.outDestroyFlag = aZone:getStringFromZoneProperty( "objectDestroyed!", "*none") aZone.outDestroyFlag = aZone:getStringFromZoneProperty( "objectDestroyed!", "*none")
end end
--PlayerScore interface (data)
if aZone:hasProperty("redScore") then
aZone.redScore = aZone:getNumberFromZoneProperty("redScore", 0)
-- if aZone.verbose then
-- trigger.action.outText("")
-- end
end
if aZone:hasProperty("blueScore") then
aZone.blueScore = aZone:getNumberFromZoneProperty("blueScore", 0)
end
return true return true
end end
--
-- Interface with PlayerScore
-- ==========================
-- PlayerScore invokes us when it finds a scenery object was killed
-- we check if it is one of ours, and if so, if a score is attached
-- for that side
function cfxObjectDestructDetector.playerScoreForKill(theObject, killSide)
if not theObject then return nil end
if not killSide then return nil end
local pos = theObject:getPoint()
local desc = theObject:getDesc()
if not desc then return nil end
desc = desc.typeName -- deref type name to match zone objName
if not desc then return end
for idx, theZone in pairs (cfxObjectDestructDetector.objectZones) do
-- see if we can find a matching ODD
if (not theZone.isDestroyed) -- make sure it's not a dupe
and theZone.objName == desc
and theZone:pointInZone(pos)
then
-- yes, ODD tracks this object
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("OOD: score invocation for hit scenery object <" .. desc .. ">, tracked with <" .. theZone.name .. ">", 30)
end
if killSide == 1 then return theZone.redScore end -- can be nil!
if killSide == 2 then return theZone.blueScore end
-- if we get here, the object is tracked, but has no
-- playerScore attached. simply exist with nil
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("OOD: scenery object <" .. desc .. ">, tracked but no player score defined for coa <" .. killSide .. ">.", 30)
end
return nil
end
end
return nil
end
-- --
-- ON EVENT -- ON EVENT
-- --
@ -106,7 +161,7 @@ function cfxObjectDestructDetector:onEvent(event)
-- invoke callbacks -- invoke callbacks
cfxObjectDestructDetector.invokeCallbacksFor(aZone) cfxObjectDestructDetector.invokeCallbacksFor(aZone)
if aZone.verbose or cfxObjectDestructDetector.verbose then if aZone.verbose or cfxObjectDestructDetector.verbose then
trigger.action.outText("OBJECT KILL: " .. id, 30) trigger.action.outText("OBJECT KILL: " .. matchMe .. " for odd <" .. aZone.name .. ">", 30)
end end
-- save state for persistence -- save state for persistence
aZone.isDestroyed = true aZone.isDestroyed = true

View File

@ -12,6 +12,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
- sceneryObject detection improvements - sceneryObject detection improvements
- DCS 2.9 safe - DCS 2.9 safe
3.0.1 - cleanup 3.0.1 - cleanup
3.0.2 - interface with ObjectDestructDetector for scoring scenery objects
--]]-- --]]--
@ -208,8 +209,9 @@ function cfxPlayerScore.cat2BaseScore(inCat)
return 1 return 1
end end
function cfxPlayerScore.object2score(inVictim) -- does not have group function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if not inVictim then return 0 end if not inVictim then return 0 end
if not killSide then killSide = -1 end
local inName = inVictim:getName() local inName = inVictim:getName()
if dcsCommon.isSceneryObject(inVictim) then if dcsCommon.isSceneryObject(inVictim) then
local desc = inVictim:getDesc() local desc = inVictim:getDesc()
@ -217,6 +219,12 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
-- same as object destruct detector to -- same as object destruct detector to
-- avoid ID changes -- avoid ID changes
inName = desc.typeName inName = desc.typeName
if cfxObjectDestructDetector then
-- ask ODD if it knows the object and what score was
-- awarded for a kill from that side
local objectScore = cfxObjectDestructDetector.playerScoreForKill(inVictim, killSide)
if objectScore then return objectScore end
end
end end
if not inName then return 0 end if not inName then return 0 end
if type(inName) == "number" then if type(inName) == "number" then
@ -744,7 +752,7 @@ function cfxPlayerScore.killDetected(theEvent)
if wasBuilding then if wasBuilding then
-- these objects have no coalition; we simply award the score if -- these objects have no coalition; we simply award the score if
-- it exists in look-up table. -- it exists in look-up table.
local staticScore = cfxPlayerScore.object2score(victim) local staticScore = cfxPlayerScore.object2score(victim, killSide)
if staticScore > 0 then if staticScore > 0 then
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound) trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName) cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName)
@ -754,8 +762,10 @@ function cfxPlayerScore.killDetected(theEvent)
end end
-- was it fratricide? -- was it fratricide?
-- if we get here, it CANT be a scenery object
-- but can be a static object, and stO have a coalition
local vicSide = victim:getCoalition() local vicSide = victim:getCoalition()
local fraternicide = killSide == vicSide local fraternicide = (killSide == vicSide)
local vicDesc = victim:getTypeName() local vicDesc = victim:getTypeName()
local scoreMod = 1 -- start at one local scoreMod = 1 -- start at one
@ -767,7 +777,7 @@ function cfxPlayerScore.killDetected(theEvent)
-- static objects have no group -- static objects have no group
local staticName = victim:getName() -- on statics, this returns local staticName = victim:getName() -- on statics, this returns
-- name as entered in TOP LINE -- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim) local staticScore = cfxPlayerScore.object2score(victim, killSide)
if staticScore > 0 then if staticScore > 0 then
-- this was a named static, return the score - unless our own -- this was a named static, return the score - unless our own
@ -1488,6 +1498,7 @@ score zones
- zones outside of which no scoring counts, but feats are still ok - zones outside of which no scoring counts, but feats are still ok
- add take off feats - add take off feats
- integrate with objectDestructDetector
can be extended with other, standalone feat modules that follow the can be extended with other, standalone feat modules that follow the
same pattern, e.g. enter a zone, detect someone same pattern, e.g. enter a zone, detect someone

View File

@ -1,8 +1,8 @@
-- theDebugger -- theDebugger 2.x
debugger = {} debugger = {}
debugger.version = "2.0.0" debugger.version = "2.1.0"
debugDemon = {} debugDemon = {}
debugDemon.version = "2.0.0" debugDemon.version = "2.1.0"
debugger.verbose = false debugger.verbose = false
debugger.ups = 4 -- every 0.25 second debugger.ups = 4 -- every 0.25 second
@ -33,6 +33,13 @@ debugger.log = ""
- debuggerSpawnTypes zone - debuggerSpawnTypes zone
- reading debuggerSpawnTypes - reading debuggerSpawnTypes
- removed some silly bugs / inconsistencies - removed some silly bugs / inconsistencies
2.1.0 - debugging code is now invoked deferred to avoid
DCS crash after exiting. Debug code now executes
outside of the event code's bracket.
debug invocation on clone of data structure
readback verification of flag set
fixed getProperty() in debugger with zone
--]]-- --]]--
debugger.requiredLibs = { debugger.requiredLibs = {
@ -201,7 +208,7 @@ end
function debugger.createDebuggerWithZone(theZone) function debugger.createDebuggerWithZone(theZone)
-- watchflag input trigger -- watchflag input trigger
theZone.debugInputMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") theZone.debugInputMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if theZone.hasProperty("debugTriggerMethod") then if theZone:hasProperty("debugTriggerMethod") then
theZone.debugInputMethod = theZone:getStringFromZoneProperty("debugTriggerMethod", "change") theZone.debugInputMethod = theZone:getStringFromZoneProperty("debugTriggerMethod", "change")
elseif theZone:hasProperty("inputMethod") then elseif theZone:hasProperty("inputMethod") then
theZone.debugInputMethod = theZone:getStringFromZoneProperty(theZone, "inputMethod", "change") theZone.debugInputMethod = theZone:getStringFromZoneProperty(theZone, "inputMethod", "change")
@ -644,7 +651,8 @@ debugDemon.splitDelimiter = " "
debugDemon.commandTable = {} -- key, value pair for command processing per keyword debugDemon.commandTable = {} -- key, value pair for command processing per keyword
debugDemon.keepOpen = false -- keep mark open after a successful command debugDemon.keepOpen = false -- keep mark open after a successful command
debugDemon.snapshot = {} debugDemon.snapshot = {}
debugDemon.activeIdx = -1 -- to detect if a window was close
-- and prevent execution of debugger
function debugDemon.hasMark(theString) function debugDemon.hasMark(theString)
-- check if the string begins with the sequece to identify commands -- check if the string begins with the sequece to identify commands
if not theString then return false end if not theString then return false end
@ -676,6 +684,7 @@ function debugDemon:onEvent(theEvent)
end end
if theEvent.id == world.event.S_EVENT_MARK_CHANGE then 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 -- when changed, the mark's text is examined for a command
-- if it starts with the 'mark' string ("-" by default) it is processed -- if it starts with the 'mark' string ("-" by default) it is processed
-- by the command processor -- by the command processor
@ -683,12 +692,19 @@ function debugDemon:onEvent(theEvent)
-- else an error is displayed and the mark remains. -- else an error is displayed and the mark remains.
if debugDemon.hasMark(theEvent.text) then if debugDemon.hasMark(theEvent.text) then
-- strip the mark -- strip the mark
local commandString = theEvent.text:sub(1+debugDemon.markOfDemon:len()) local cCommand = dcsCommon.clone(theEvent.text, true)
local commandString = cCommand:sub(1+debugDemon.markOfDemon:len())
-- break remainder apart into <command> <arg1> ... <argn> -- break remainder apart into <command> <arg1> ... <argn>
local commands = dcsCommon.splitString(commandString, debugDemon.splitDelimiter) local commands = dcsCommon.splitString(commandString, debugDemon.splitDelimiter)
-- this is a command. process it and then remove it if it was executed successfully -- this is a command. process it and then remove it if it was executed successfully
local success = debugDemon.executeCommand(commands, theEvent) local cTheEvent = dcsCommon.clone(theEvent, true) -- 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 -- remove this mark after successful execution
if success then if success then
@ -696,10 +712,32 @@ function debugDemon:onEvent(theEvent)
else else
-- we could play some error sound -- we could play some error sound
end end
--]]--
end end
end end
if theEvent.id == world.event.S_EVENT_MARK_REMOVED then 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
-- remove this mark after successful execution
if success then
trigger.action.removeMark(cTheEvent.idx)
debugDemon.activeIdx = nil
else
-- we could play some error sound
end end
end end
@ -1103,6 +1141,11 @@ function debugDemon.processSetCommand(args, event)
debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">" .. note, 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">" .. note, 30)
local newVal = trigger.misc.getUserFlag(theName)
if theVal ~= newVal then
debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: readback failure for flag <" .. theName .. ">: expected <" .. theVal .. ">, got <" .. newVal .. "!", 30)
end
return true return true
end end

View File

@ -1,5 +1,5 @@
williePete = {} williePete = {}
williePete.version = "2.0.0" williePete.version = "2.0.2"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters, -- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters -- with interpolation even at 3 meters
@ -17,6 +17,8 @@ williePete.requiredLibs = {
2.0.0 - dmlZones, OOP 2.0.0 - dmlZones, OOP
- Guards for multi-unit player groups - Guards for multi-unit player groups
- getFirstLivingPlayerInGroupNamed() - getFirstLivingPlayerInGroupNamed()
2.0.1 - added Harrier's FFAR M156 WP
2.0.2 - hardened playerUpdate()
--]]-- --]]--
williePete.willies = {} williePete.willies = {}
@ -28,7 +30,7 @@ williePete.blastedObjects = {} -- used when we detonate something
-- recognizes WP munitions. May require regular update when new -- recognizes WP munitions. May require regular update when new
-- models come out. -- models come out.
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM", "SNEB_TYPE254_H1_GREEN", "SNEB_TYPE254_H1_RED", "SNEB_TYPE254_H1_YELLOW"} williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM", "SNEB_TYPE254_H1_GREEN", "SNEB_TYPE254_H1_RED", "SNEB_TYPE254_H1_YELLOW", "FFAR M156 WP"}
function williePete.addWillie(theWillie) function williePete.addWillie(theWillie)
table.insert(williePete.willies, theWillie) table.insert(williePete.willies, theWillie)
@ -617,6 +619,7 @@ function williePete.playerUpdate()
-- make sure at least one unit still exists -- make sure at least one unit still exists
local dropUnit = true local dropUnit = true
local theGroup = Group.getByName(unitInfo.gName) local theGroup = Group.getByName(unitInfo.gName)
if theGroup then
local allUnits = theGroup:getUnits() local allUnits = theGroup:getUnits()
for idx, theUnit in pairs(allUnits) do for idx, theUnit in pairs(allUnits) do
--local theUnit = Unit.getByName(unitInfo.name) --local theUnit = Unit.getByName(unitInfo.name)
@ -631,6 +634,9 @@ function williePete.playerUpdate()
end end
end end
end end
else
trigger.action.outText("+++wp: strange issues with group <" .. gName .. ">, does not exist. Skipped in playerUpdate()", 30)
end
if dropUnit then if dropUnit then
-- all outside, remove from zone check-in -- all outside, remove from zone check-in
-- williePete.doCheckOut(unitInfo) -- williePete.doCheckOut(unitInfo)

Binary file not shown.