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
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
--]]

View File

@ -158,3 +158,13 @@ if not cfxSSBSingleUse.start()then
trigger.action.outText("SSB Single Use failed to start up.", 30)
cfxSSBSingleUse = nil
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.version = "1.0.2"
tdz.version = "1.0.3"
tdz.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -17,7 +17,8 @@ VERSION HISTORY
helo attribute
1.0.2 - manual placement option
filters FARPs
1.0.3 - "manual" now defaults to false
--]]--
tdz.allTdz = {}
@ -89,7 +90,7 @@ function tdz.createTDZ(theZone)
local nearestRwy = nil
-- 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
if theZone.verbose or tdz.verbose then
trigger.action.outText("+++TDZ: runway for <" .. theZone.name .. "> is manually placed", 30)

View File

@ -1,34 +1,26 @@
airfield = {}
airfield.version = "2.0.0"
airfield.version = "2.1.1"
airfield.requiredLibs = {
"dcsCommon",
"cfxZones",
}
airfield.myAirfields = {} -- indexed by name
airfield.myAirfields = {} -- indexed by af name, zone that links to it
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,
can force the coalition of an airfield, and always provides the
current owner as a value
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
Version History^
2.0.0 - show all airfields option
- fully reworked show options
- unmanaged airfields are automatically updated
- full color support
-- 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
for idx, aBase in pairs(allBases) do
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
if (cat == 0) or (cat == 1) then
local name = aBase:getName()
@ -50,6 +44,7 @@ function airfield.collectAll()
count = count + 1
else
dropped = dropped + 1
-- trigger.action.outText("***dropped airbase <" .. aBase:getName() .. ">, cat = <" .. cat .. ">", 30)
end
end
if airfield.verbose then
@ -98,6 +93,11 @@ function airfield.createAirFieldFromZone(theZone)
theZone.lastMakeBlue = trigger.misc.getUserFlag(theZone.makeBlue)
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
theZone.autoCap = theZone:getStringFromZoneProperty("autoCap?", "<none>")
theZone.lastAutoCap = trigger.misc.getUserFlag(theZone.autoCap)
@ -146,8 +146,11 @@ function airfield.createAirFieldFromZone(theZone)
-- now mark this zone as handled
local entry = airfield.allAirfields[theZone.afName]
entry.linkedTo = theZone -- only remember last, but that's enough.
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.
end
end
function airfield.markAirfieldOnMap(theAirfield, lineColor, fillColor)
@ -246,6 +249,10 @@ function airfield.airfieldCaptured(theBase)
airfield.untendedCapture(bName, theBase)
return
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()
theZone.owner = newCoa
@ -323,6 +330,21 @@ function airfield.update()
airfield.airfieldCaptured(theAirfield)
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 theAirfield:autoCaptureIsOn() then

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.1.1"
cfxZones.version = "4.1.2"
-- cf/x zone management module
-- 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.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates
- 4.1.2 - hash property missing warning
--]]--
@ -2299,6 +2300,14 @@ function dmlZone:hasProperty(theProperty)
return false
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
end
return true

View File

@ -1,37 +1,6 @@
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
default liveries for Yak-50 (main test case)
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
protest action
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)
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
theZone.turn = theZone:getNumberFromZoneProperty("turn", 0)

View File

@ -1,70 +1,9 @@
csarManager = {}
csarManager.version = "2.3.2"
csarManager.verbose = false
csarManager.version = "3.0.0"
csarManager.ups = 1
--[[-- 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
- onRoad attribute for CSAR mission Zones
- rndLoc support
@ -74,32 +13,27 @@ csarManager.ups = 1
- offset zone on randomized soldier
- smokeDist
- 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 WITH LIMITE AIRFRAMES IF INSTALLED
--]]--
-- modules that need to be loaded BEFORE I run
csarManager.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
"cfxZones", -- zones management for CSAR and CSAR Mission zones
"cfxPlayer", -- player monitoring and group monitoring
"nameStats", -- generic data module for weight
"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
-- currently in the game. The Array is indexed by unit name
csarManager.unitConfigs = {}
csarManager.myEvents = {3, 4, 5} -- 3 = take off, 4 = land, 5 = crash
--
-- CASR MISSION
@ -156,16 +90,11 @@ function csarManager.createDownedPilot(theMission, existingUnit)
aLocation.x,
aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards
-- WARNING:
-- 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
local theSideCJTF = dcsCommon.getACountryForCoalition(theMission.side)
theMission.group = coalition.addGroup(theSideCJTF,
Group.Category.GROUND,
theBoyGroup)
if theBoyGroup then
else
@ -176,10 +105,10 @@ function csarManager.createDownedPilot(theMission, existingUnit)
end
-- 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
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)
cmd = cfxCommander.createTransmissionCommand(csarManager.beaconSound)
cfxCommander.addCommand(theCommands, cmd)
@ -259,7 +188,7 @@ end
-- UNIT CONFIG
--
function csarManager.resetConfig(conf)
-- reset only ovberwrites mission-relevant data
-- reset only overwrites mission-relevant data
conf.troopsOnBoard = {} -- number of rescued missions
local myName = conf.name
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
--
function csarManager.isInteresting(eventID)
-- 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)
function csarManager:onEvent(event)
-- 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
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 cfxPlayer.isPlayerUnit(theUnit) then
--trigger.action.outText("+++csar: rejected event: " .. theUnit:getName() .. " not a player helo", 30)
return false
end -- not a player unit
return csarManager.isInteresting(event.id)
end
if not dcsCommon.isPlayerUnit(theUnit) then return end -- not a player unit
function csarManager.postProcessor(event)
-- don't do anything for now
end
-- only proceed if troop carrier (no more helo checks, all troop carriers, so osprey and harrier can be used if so desired)
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
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 myType = theUnit:getTypeName()
if ID == 4 then -- landed
csarManager.heloLanded(theUnit)
end
@ -360,17 +259,18 @@ function csarManager.somethingHappened(event)
if ID == 5 then -- crash
csarManager.heloCrashed(theUnit)
end
if ID == 15 then -- player helicopter birth
-- we need to set up comms for this unit
csarManager.setCommsMenu(theUnit)
end
csarManager.setCommsMenu(theUnit)
end
--
--
-- CSAR LANDED
--
--
function csarManager.successMission(who, where, theMission)
-- who is
-- where is
@ -397,7 +297,6 @@ function csarManager.successMission(who, where, theMission)
-- now call callback for coalition side
-- callback has format callback(coalition, success true/false, numberSaved, descriptionText)
csarManager.invokeCallbacks(theMission.side, true, 1, "success")
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
-- we do *not* return so we can pick up troops on
-- a CSARBASE if they were dropped there
else
if csarManager.verbose or base.zone.verbose then
trigger.action.outText("+++csar: touchdown of <" .. myName .. "> occured outside of csar zone <" .. base.zone.name .. ">", 30)
end
end
else -- not on my side
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)
@ -930,46 +827,6 @@ function csarManager.directions(args)
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
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
@ -1056,6 +913,10 @@ function csarManager.launchFlare(args)
trigger.action.signalFlare(loc, color, 0)
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
-- schedule next invocation
timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups)
@ -1065,147 +926,161 @@ function csarManager.update() -- every second
-- now scan through all helo groups and see if they are close to a
-- CSAR zone and initiate the help sequence
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
if aUnit:isExist() and aUnit:inAir() then -- exists and is flying
-- local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
local allPlayerUnits = dcsCommon.getAllExistingPlayersAndUnits() -- indexed by player name
--old -- contains per group a player record, use prime unit to access player's unit
for pname, aUnit in pairs(allPlayerUnits) do
if --aUnit:isExist() and
aUnit:inAir() and
dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers)
then -- troop carrier and is flying
local uPoint = aUnit:getPoint()
local uName = aUnit:getName()
local uGroup = aUnit:getGroup()
local uID = uGroup:getID()
local uSide = aUnit:getCoalition()
local agl = dcsCommon.getUnitAGL(aUnit)
if dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers) then
-- scan through all available csar missions to see if we are close
-- enough to trigger comms
for idx, csarMission in pairs (csarManager.openMissions) do
-- check if we are inside trigger range on the same side
local mp = cfxZones.getPoint(csarMission.zone, true)
local d = dcsCommon.distFlat(uPoint, mp)
if ((uSide == csarMission.side) or (csarMission.side == 0) )
and (d < csarManager.rescueTriggerRange) then
-- we are in trigger distance. if we did not notify before
-- do it now
if not dcsCommon.arrayContainsString(csarMission.messagedUnits, uName) then
-- radio this unit with oclock and tell it they are in 2k range
-- also note if LZ is hot
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(csarMission.zone.point, uPoint, ownHeading) .. " o'clock"
local msg = "\n" .. uName ..", " .. csarMission.name .. ". We can hear you, check your " .. oclock
if csarManager.useSmoke then msg = msg .. " - popping smoke" end
if csarManager.useFlare then
if csarManager.useSmoke then
msg = msg .. " and will launch flare in a few seconds"
else
msg = msg .. " - preparing flare"
end
-- schedule flare launch in 5-10 seconds
local args = {}
args.loc = mp
args.color = csarManager.flareColor
args.uID = uID
timer.scheduleFunction(csarManager.launchFlare, args, timer.getTime() + math.random(5))
end
msg = msg .. "."
if csarMission.isHot then
msg = msg .. " Be advised: LZ is hot."
end
msg = msg .. "\n"
trigger.action.outTextForGroup(uID, msg, 30)
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
end
-- also pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
local smokePoint = dcsCommon.randomPointOnPerimeter(
csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z) --cfxZones.createHeightCorrectedPoint(csarMission.zone.point)
-- trigger.action.smoke(smokePoint, 4 )
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor)
csarMission.lastSmokeTime = timer.getTime()
end
-- now check if we are inside hover range and alt
-- in order to simultate winch ops
-- WARNING: WE ALWAYS ONLY CHECK A SINGLE UNIT - the first alive
local evacuee = csarMission.group:getUnit(1)
if evacuee then
local ep = evacuee:getPoint()
d = dcsCommon.distFlat(uPoint, ep)
d = math.floor(d * 10) / 10
if d < csarManager.rescueTriggerRange * 0.5 then --csarManager.hoverRadius * 2 then
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock"
-- log distance
local hoverMsg = "Closing on " .. csarMission.name .. ", " .. d * 1 .. "m on your " .. oclock .. " o'clock"
if d < csarManager.hoverRadius then
if (agl <= csarManager.hoverAlt) and (agl > 3) then
local hoverTime = csarMission.hoveringUnits[uName]
if not hoverTime then
-- create new entry
hoverTime = timer.getTime()
csarMission.hoveringUnits[uName] = timer.getTime()
end
hoverTime = timer.getTime() - hoverTime -- calculate number of seconds
local remainder = math.floor(csarManager.hoverDuration - hoverTime)
if remainder < 1 then remainder = 1 end
hoverMsg = "Steady... " .. d * 1 .. "m to your " .. oclock .. " o'clock, winching... (" .. remainder .. ")"
if hoverTime > csarManager.hoverDuration then
-- we rescued the guy!
hoverMsg = "We have " .. csarMission.name .. " safely on board!"
local conf = csarManager.getUnitConfig(aUnit)
csarManager.removeMission(csarMission)
table.insert(conf.troopsOnBoard, csarMission)
csarMission.group:destroy() -- will shut up radio as well
csarMission.group = nil
-- now handle weight using cargoSuper
local theMassObject = cargoSuper.createMassObject(
csarManager.pilotWeight,
csarMission.name,
csarMission)
cargoSuper.addMassObjectTo(
uName,
"Evacuees",
theMassObject)
local totalMass = cargoSuper.calculateTotalMassFor(uName)
trigger.action.setUnitInternalCargo(uName, totalMass)
if csarManager.verbose then
local allEvacuees = cargoSuper.getManifestFor(myName, "Evacuees") -- returns unlinked array
trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30)
end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, csarManager.actionSound) --"Quest Snare 3.wav")
return -- we only ever rescue one
end -- hovered long enough
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op
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"
csarMission.hoveringUnits[uName] = nil -- reset timer
end
else -- not inside hover dist
-- remove the hover indicator for this
csarMission.hoveringUnits[uName] = nil
end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op
local needsGC = false
-- local hasMessaged = false
for idx, csarMission in pairs (csarManager.openMissions) do
-- check if we are inside trigger range on the same side
local mp = cfxZones.getPoint(csarMission.zone, true)
local d = dcsCommon.distFlat(uPoint, mp)
if ((uSide == csarMission.side) or (csarMission.side == 0) )
and (d < csarManager.rescueTriggerRange) then
-- we are in trigger distance. if we did not notify before
-- do it now, we ever only do this once for a unit for any mission
if not dcsCommon.arrayContainsString(csarMission.messagedUnits, uName) then
-- radio this unit with oclock and tell it they are in 2k range
-- also note if LZ is hot
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(csarMission.zone.point, uPoint, ownHeading) .. " o'clock"
local msg = "\n" .. uName ..", " .. csarMission.name .. ". We can hear you, check your " .. oclock
if csarManager.useSmoke then msg = msg .. " - popping smoke" end
if csarManager.useFlare then
if csarManager.useSmoke then
msg = msg .. " and will launch flare in a few seconds"
else
-- remove the hover indicator for this unit
csarMission.hoveringUnits[uName] = nil
end -- inside 2 * hover dist?
end -- has evacuee
end -- if in range
end -- for all missions
end -- if troop carrier
end -- if exists
end -- for all players
msg = msg .. " - preparing flare"
end
-- schedule flare launch in 5-10 seconds
local args = {}
args.loc = mp
args.color = csarManager.flareColor
args.uID = uID
timer.scheduleFunction(csarManager.launchFlare, args, timer.getTime() + math.random(5))
end
msg = msg .. "."
if csarMission.isHot then
msg = msg .. " Be advised: LZ is hot."
end
msg = msg .. "\n"
trigger.action.outTextForGroup(uID, msg, 30)
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
end
-- also pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
local smokePoint = dcsCommon.randomPointOnPerimeter(
csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z)
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor)
csarMission.lastSmokeTime = timer.getTime()
end
-- now check if we are inside hover range and alt
-- in order to simultate winch ops
-- if competition picked up, we skip this loop
local evacuee = nil
if csarMission.group then evacuee = csarMission.group:getUnit(1) end
if evacuee then
local ep = evacuee:getPoint()
d = dcsCommon.distFlat(uPoint, ep)
d = math.floor(d * 10) / 10
if d < csarManager.rescueTriggerRange * 0.5 then
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock"
-- log distance
local hoverMsg = "Closing on " .. csarMission.name .. ", " .. d * 1 .. "m on your " .. oclock .. " o'clock"
if d < csarManager.hoverRadius then
if (agl <= csarManager.hoverAlt) and (agl > 3) then
local hoverTime = csarMission.hoveringUnits[uName]
if not hoverTime then
-- create new entry
hoverTime = timer.getTime()
csarMission.hoveringUnits[uName] = timer.getTime()
end
hoverTime = timer.getTime() - hoverTime -- calculate number of seconds
local remainder = math.floor(csarManager.hoverDuration - hoverTime)
if remainder < 1 then remainder = 1 end
hoverMsg = "Steady... " .. d * 1 .. "m to your " .. oclock .. " o'clock, winching... (" .. remainder .. ")"
if hoverTime > csarManager.hoverDuration then
-- we rescued the guy!
hoverMsg = "We have " .. csarMission.name .. " safely on board!"
local conf = csarManager.getUnitConfig(aUnit)
-- mission now GC's after iteration csarManager.removeMission(csarMission)
table.insert(conf.troopsOnBoard, csarMission)
csarMission.group:destroy() -- will shut up radio as well
csarMission.group = nil -- no more evacuees
needsGC = true -- need filtering missions
-- now handle weight using cargoSuper
local theMassObject = cargoSuper.createMassObject(
csarManager.pilotWeight,
csarMission.name,
csarMission)
cargoSuper.addMassObjectTo(
uName,
"Evacuees",
theMassObject)
local totalMass = cargoSuper.calculateTotalMassFor(uName)
trigger.action.setUnitInternalCargo(uName, totalMass)
if csarManager.verbose then
local allEvacuees = cargoSuper.getManifestFor(myName, "Evacuees") -- returns unlinked array
trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30)
end
--trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, csarManager.actionSound)
--return -- we only ever rescue one
end -- hovered long enough
--trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
-- return -- only ever one winch op
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"
csarMission.hoveringUnits[uName] = nil -- reset timer
end
else -- not inside hover dist
-- remove the hover indicator for this
csarMission.hoveringUnits[uName] = nil
end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
--return -- only ever one winch op
else
-- remove the hover indicator for this unit
csarMission.hoveringUnits[uName] = nil
end -- inside 2 * hover dist?
else
-- somebody snatched the evacuee
end -- if has evacuee
end -- if in range
end -- for all missions
-- now GC all missions if we lifted a pilot up (we no longer return after first succesful)
if needsGC then
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
-- that has been told to spawn
@ -1216,32 +1091,37 @@ function csarManager.update() -- every second
-- local currVal = theZone:getFlagValue(theZone.startCSAR)
-- if currVal ~= theZone.lastCSARVal then
if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
-- set up random point in zone
local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
end
local theMission = csarManager.createCSARMissionData(
mPoint,
theZone.csarSide, -- theSide
theZone.csarFreq, -- freq
theZone.csarName, -- name
theZone.numCrew, -- numCrew
theZone.timeLimit, -- timeLimit
theZone.csarMapMarker, -- mapMarker
0.1, --theZone.radius) -- radius
nil) -- parashoo unit
local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission)
--theZone.lastCSARVal = currVal
if csarManager.verbose then
trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30)
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
local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
end
local theMission = csarManager.createCSARMissionData(
mPoint,
theZone.csarSide, -- theSide
theZone.csarFreq, -- freq
theZone.csarName, -- name
theZone.numCrew, -- numCrew
theZone.timeLimit, -- timeLimit
theZone.csarMapMarker, -- mapMarker
0.1, --theZone.radius) -- radius
nil) -- parashoo unit
return theMission
end
--
-- create a CSAR Mission for a unit
--
@ -1412,9 +1292,6 @@ function csarManager.readConfigZone()
csarManager.name = "csarManagerConfig" -- compat with cfxZones
local theZone = cfxZones.getZoneByName("csarManagerConfig")
if not theZone then
if csarManager.verbose then
trigger.action.outText("+++csar: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("csarManagerConfig")
end
csarManager.configZone = theZone -- save for flag banging compatibility
@ -1487,38 +1364,34 @@ function csarManager.start()
-- read config
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
csarManager.processCSARBASE()
-- now scan all zones to create ME-placed CSAR missions
-- and populate the available mission.
csarManager.processCSARZones()
-- install callbacks for helo-relevant events
--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
-- now call update so we can monitor progress of all helos, and alert them
-- when they are close to a CSAR
-- start updating and track all helicopters in the air against missions
csarManager.update()
-- 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
end
-- let's get rolling
if not csarManager.start() then
trigger.action.outText("cf/x CSAR Manager v" .. csarManager.version .. " FAILED to run", 30)
csarManager = nil
end
@ -1539,6 +1412,7 @@ end
- when unloading one by menu, update weight!!!
-- allow any airfied to be csarsafe by default, no longer *requires* csarbase
-- minFreq, maxFreq settings for config and mission-individual
-- remove cfxPlayer dependency
--]]--

View File

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

View File

@ -1,5 +1,5 @@
factoryZone = {}
factoryZone.version = "3.0.0"
factoryZone.version = "3.1.0"
factoryZone.verbose = false
factoryZone.name = "factoryZone"
@ -11,6 +11,12 @@ factoryZone.name = "factoryZone"
- use maxRadius from zone for spawning to support quad zones
3.0.0 - support for liveries via "factoryLiveries" zone
- OOP dmlZones
3.1.0 - redD!, blueD!
- redP!, blueP!
- method
- productionTime config synonyme
- defendMe? attribute
- triggered 'shocked' mode via defendMe
--]]--
factoryZone.requiredLibs = {
@ -104,16 +110,55 @@ function factoryZone.addFactoryZone(aZone)
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "factoryTriggerMethod", "change")
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.verifyZone(aZone)
end
function factoryZone.verifyZone(aZone)
-- do some sanity checks
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
trigger.action.outText("+++factZ: " .. aZone.name .. " attackers need cfxGroundTroops to function", 30)
end
-- if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
-- now can also bang on flags, no more verification
-- unless we want to beef them up
-- end
end
function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
@ -183,6 +228,7 @@ end
--
function factoryZone.sendOutAttackers(aZone)
-- sanity check: never done for neutral zones
if aZone.owner == 0 then
if aZone.verbose or factoryZone.verbose then
@ -193,16 +239,31 @@ function factoryZone.sendOutAttackers(aZone)
-- only spawn if there are zones to attack
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)
end
return
end
if factoryZone.verbose then
if factoryZone.verbose or aZone.verbose then
trigger.action.outText("+++factZ - attack cycle for ".. aZone.name, 30)
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
local attackers = aZone.attackersRED;
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
local unitTypes = {} -- build type names
-- if none, we are done
if defenders == "none" then return end
-- if none, we are done, save for the outputs
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
allTypes = dcsCommon.trimArray(
@ -308,14 +384,32 @@ end
function factoryZone.spawnDefenders(aZone)
-- 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.verbose or factoryZone.verbose then
trigger.action.outText("+++factZ: spawnDefenders invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
end
return
end
-- bang! on xxxD!
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
-- before we spawn new defenders, remove the old ones
@ -388,7 +482,7 @@ function factoryZone.updateZoneProduction(aZone)
aZone.defenders then
-- we have defenders
if aZone.defenders:isExist() then
-- isee if group was damaged
-- see if group was damaged
if not aZone.lastDefenders then
-- fresh group, probably from persistence, needs init
aZone.lastDefenders = -1
@ -458,7 +552,7 @@ function factoryZone.updateZoneProduction(aZone)
if timer.getTime() > aZone.timeStamp + factoryZone.repairTime then
aZone.timeStamp = timer.getTime()
-- 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
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
@ -467,7 +561,14 @@ function factoryZone.updateZoneProduction(aZone)
aZone.timeStamp = timer.getTime()
if factoryZone.verbose then
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
@ -540,6 +641,19 @@ function factoryZone.update()
if theZone.activateFlag and cfxZones.testZoneFlag(theZone, theZone.activateFlag, theZone.factoryTriggerMethod, "lastActivateValue") then
theZone.paused = false
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
factoryZone.updateZoneProduction(theZone)
end -- iterating all zones
@ -677,6 +791,9 @@ function factoryZone.readConfigZone(theZone)
factoryZone.verbose = theZone.verbose
factoryZone.defendingTime = theZone:getNumberFromZoneProperty( "defendingTime", 100)
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.repairTime = theZone:getNumberFromZoneProperty( "repairTime", 200)
factoryZone.targetZones = "OWNED"

View File

@ -1,5 +1,5 @@
cfxObjectDestructDetector = {}
cfxObjectDestructDetector.version = "2.0.0"
cfxObjectDestructDetector.version = "2.0.2"
cfxObjectDestructDetector.verbose = false
cfxObjectDestructDetector.requiredLibs = {
"dcsCommon", -- always
@ -18,6 +18,10 @@ cfxObjectDestructDetector.requiredLibs = {
ID changes (happens with map updates)
fail addZone when name property is missing
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 = {}
@ -78,8 +82,59 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
elseif aZone:hasProperty("objectDestroyed!") then
aZone.outDestroyFlag = aZone:getStringFromZoneProperty( "objectDestroyed!", "*none")
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
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
--
@ -106,7 +161,7 @@ function cfxObjectDestructDetector:onEvent(event)
-- invoke callbacks
cfxObjectDestructDetector.invokeCallbacksFor(aZone)
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
-- save state for persistence
aZone.isDestroyed = true

View File

@ -12,6 +12,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
- sceneryObject detection improvements
- DCS 2.9 safe
3.0.1 - cleanup
3.0.2 - interface with ObjectDestructDetector for scoring scenery objects
--]]--
@ -208,8 +209,9 @@ function cfxPlayerScore.cat2BaseScore(inCat)
return 1
end
function cfxPlayerScore.object2score(inVictim) -- does not have group
if not inVictim then return 0 end
function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if not inVictim then return 0 end
if not killSide then killSide = -1 end
local inName = inVictim:getName()
if dcsCommon.isSceneryObject(inVictim) then
local desc = inVictim:getDesc()
@ -217,6 +219,12 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
-- same as object destruct detector to
-- avoid ID changes
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
if not inName then return 0 end
if type(inName) == "number" then
@ -744,7 +752,7 @@ function cfxPlayerScore.killDetected(theEvent)
if wasBuilding then
-- these objects have no coalition; we simply award the score if
-- it exists in look-up table.
local staticScore = cfxPlayerScore.object2score(victim)
local staticScore = cfxPlayerScore.object2score(victim, killSide)
if staticScore > 0 then
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName)
@ -754,8 +762,10 @@ function cfxPlayerScore.killDetected(theEvent)
end
-- 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 fraternicide = killSide == vicSide
local fraternicide = (killSide == vicSide)
local vicDesc = victim:getTypeName()
local scoreMod = 1 -- start at one
@ -767,7 +777,7 @@ function cfxPlayerScore.killDetected(theEvent)
-- static objects have no group
local staticName = victim:getName() -- on statics, this returns
-- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim)
local staticScore = cfxPlayerScore.object2score(victim, killSide)
if staticScore > 0 then
-- 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
- add take off feats
- integrate with objectDestructDetector
can be extended with other, standalone feat modules that follow the
same pattern, e.g. enter a zone, detect someone

View File

@ -1,8 +1,8 @@
-- theDebugger
-- theDebugger 2.x
debugger = {}
debugger.version = "2.0.0"
debugger.version = "2.1.0"
debugDemon = {}
debugDemon.version = "2.0.0"
debugDemon.version = "2.1.0"
debugger.verbose = false
debugger.ups = 4 -- every 0.25 second
@ -33,6 +33,13 @@ debugger.log = ""
- debuggerSpawnTypes zone
- reading debuggerSpawnTypes
- 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 = {
@ -201,7 +208,7 @@ end
function debugger.createDebuggerWithZone(theZone)
-- watchflag input trigger
theZone.debugInputMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if theZone.hasProperty("debugTriggerMethod") then
if theZone:hasProperty("debugTriggerMethod") then
theZone.debugInputMethod = theZone:getStringFromZoneProperty("debugTriggerMethod", "change")
elseif theZone:hasProperty("inputMethod") then
theZone.debugInputMethod = theZone:getStringFromZoneProperty(theZone, "inputMethod", "change")
@ -644,7 +651,8 @@ debugDemon.splitDelimiter = " "
debugDemon.commandTable = {} -- key, value pair for command processing per keyword
debugDemon.keepOpen = false -- keep mark open after a successful command
debugDemon.snapshot = {}
debugDemon.activeIdx = -1 -- to detect if a window was close
-- and prevent execution of debugger
function debugDemon.hasMark(theString)
-- check if the string begins with the sequece to identify commands
if not theString then return false end
@ -676,6 +684,7 @@ 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
@ -683,12 +692,19 @@ function debugDemon:onEvent(theEvent)
-- else an error is displayed and the mark remains.
if debugDemon.hasMark(theEvent.text) then
-- 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>
local commands = dcsCommon.splitString(commandString, debugDemon.splitDelimiter)
-- 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
if success then
@ -696,13 +712,35 @@ function debugDemon:onEvent(theEvent)
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
-- 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
--
-- add / remove commands to/from vocabulary
--
@ -1103,6 +1141,11 @@ function debugDemon.processSetCommand(args, event)
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
end

View File

@ -1,5 +1,5 @@
williePete = {}
williePete.version = "2.0.0"
williePete.version = "2.0.2"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters
@ -17,6 +17,8 @@ williePete.requiredLibs = {
2.0.0 - dmlZones, OOP
- Guards for multi-unit player groups
- getFirstLivingPlayerInGroupNamed()
2.0.1 - added Harrier's FFAR M156 WP
2.0.2 - hardened playerUpdate()
--]]--
williePete.willies = {}
@ -28,7 +30,7 @@ williePete.blastedObjects = {} -- used when we detonate something
-- recognizes WP munitions. May require regular update when new
-- 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)
table.insert(williePete.willies, theWillie)
@ -617,19 +619,23 @@ function williePete.playerUpdate()
-- make sure at least one unit still exists
local dropUnit = true
local theGroup = Group.getByName(unitInfo.gName)
local allUnits = theGroup:getUnits()
for idx, theUnit in pairs(allUnits) do
--local theUnit = Unit.getByName(unitInfo.name)
if theUnit and Unit.isExist(theUnit) and
theUnit.getPlayerName and theUnit:getPlayerName() then
local up = theUnit:getPoint()
up.y = 0
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
if isInside then
dropUnit = false
if theGroup then
local allUnits = theGroup:getUnits()
for idx, theUnit in pairs(allUnits) do
--local theUnit = Unit.getByName(unitInfo.name)
if theUnit and Unit.isExist(theUnit) and
theUnit.getPlayerName and theUnit:getPlayerName() then
local up = theUnit:getPoint()
up.y = 0
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
if isInside then
dropUnit = false
end
end
end
else
trigger.action.outText("+++wp: strange issues with group <" .. gName .. ">, does not exist. Skipped in playerUpdate()", 30)
end
if dropUnit then
-- all outside, remove from zone check-in

Binary file not shown.