Version 1.1.7

autoCSAR
williePete
ssbClient 3.0.0
This commit is contained in:
Christian Franz 2022-10-12 12:33:35 +02:00
parent 4c0b0e8bed
commit 5ec26aaaf1
13 changed files with 369 additions and 137 deletions

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,7 @@ function autoCSAR.createNewCSAR(theUnit)
-- unit has no group
local coa = theUnit:getCoalition()
if coa == 0 then -- neutral
trigger.action.outText("Neutal Pilot made it safely to ground.", 30)
trigger.action.outText("Neutral Pilot made it safely to ground.", 30)
return
end
if coa == 1 and not autoCSAR.redCSAR then

View File

@ -1,5 +1,5 @@
cfxSSBClient = {}
cfxSSBClient.version = "2.1.0"
cfxSSBClient.version = "3.0.0"
cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -19,7 +19,7 @@ Version History
1.1.1 - performance tuning. only read player groups once
- and remove in-air-start groups from scan. this requires
- ssb (server) be not modified
1.2.0 - API to close airfields: invoke openAirfieldNamed()
1.2.0 - API to close airfields: invoke openAirFieldNamed()
and closeAirfieldNamed() with name as string (exact match required)
to block an airfield for any player aircraft.
Works for FARPS as well
@ -42,6 +42,12 @@ Version History
in onEvent
2.1.0 - slotState
- persistence
3.0.0 - closing an airfield will not kick players who are active
- much better verbosity
- open?
- close?
- also persists closed airfield list
WHAT IT IS
@ -92,8 +98,59 @@ cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircraft
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying
cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane
cfxSSBClient.slotState = {} -- keeps a record of which slot has which value. For persistence and debugging
cfxSSBClient.occupiedUnits = {} -- by unit name if occupied to prevent kicking. clears after crash or leaving plane
-- will not be persisted because on start all planes are empty
-- dml zone interface for open/close interface
cfxSSBClient.clientZones = {}
function cfxSSBClient.addClientZone(theZone)
table.insert(cfxSSBClient.clientZones, theZone)
end
function cfxSSBClient.getClientZoneByName(aName)
for idx, aZone in pairs(cfxSSBClient.clientZones) do
if aName == aZone.name then return aZone end
end
if cfxSSBClient.verbose then
trigger.action.outText("+++ssbc: no client zone with name <" .. aName ..">", 30)
end
return nil
end
--
-- read client zones
--
function cfxSSBClient.createClientZone(theZone)
local thePoint = cfxZones.getPoint(theZone)
local theAF = cfxSSBClient.getClosestAirbaseTo(thePoint)
local afName = theAF:getName()
if cfxSSBClient.verbose or theZone.verbose then
trigger.action.outText("+++ssbc: zone <" .. theZone.name .. "> linked to AF/FARP <" .. afName .. ">", 30)
end
theZone.afName = afName
theZone.ssbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ssbTriggerMethod", "change")
if cfxZones.hasProperty(theZone, "open?") then
theZone.ssbOpen = cfxZones.getStringFromZoneProperty(theZone, "open?", "none")
theZone.lastSsbOpen = cfxZones.getFlagValue(theZone.ssbOpen, theZone)
end
if cfxZones.hasProperty(theZone, "close?") then
theZone.ssbClose = cfxZones.getStringFromZoneProperty(theZone, "close?", "none")
theZone.lastSsbClose = cfxZones.getFlagValue(theZone.ssbClose, theZone)
end
theZone.ssbOpenOnStart = cfxZones.getBoolFromZoneProperty(theZone, "openOnStart", true)
if not theZone.ssbOpenOnStart then
cfxSSBClient.closeAirfieldNamed(theZone.afName)
end
end
--
-- Open / Close Airfield API
--
function cfxSSBClient.closeAirfieldNamed(name)
if not name then return end
cfxSSBClient.closedAirfields[name] = true
@ -155,6 +212,25 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
if not theGroup then return end
-- WARNING: theGroup is cfxGroup record
local theName = theGroup.name
-- we now check if any plane of that group is still
-- existing and in the air. if so, we skip this check
-- to prevent players being kicked for losing their
-- originating airfield
-- we now iterate all playerUnits in theGroup.
-- theGroup is cfxGroup
for idx, playerData in pairs (theGroup.playerUnits) do
local uName = playerData.name
if cfxSSBClient.occupiedUnits[uName] then
if cfxSSBClient.verbose then
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
end
return
end
end
-- when we get here, no unit in the entire group is occupied
local theMatchingAirfield = theGroup.airfield
-- airfield was attached at startup to group
if cfxSSBClient.singleUse and cfxSSBClient.crashedGroups[theName] then
@ -194,11 +270,17 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end
end
-- set the ssb flag for this group so the server can see it
if cfxSSBClient.verbose then
local lastState = trigger.misc.getUserFlag(theName)
if lastState ~= blockState then
trigger.action.outText("+++ssbc: <" .. theName .. "> changes from <" .. lastState .. "> to <" .. blockState .. ">", 30)
trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30)
end
end
trigger.action.setUserFlag(theName, blockState)
cfxSSBClient.slotState[theName] = blockState
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30)
end
--if cfxSSBClient.verbose then
--end
else
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30)
@ -206,6 +288,18 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end
end
function cfxSSBClient.setSlotAccessForUnit(theUnit) -- calls setSlotAccessForGroup
if not theUnit then return end
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gName = theGroup:getName()
if not gName then return end
local pGroup = cfxSSBClient.getPlayerGroupForGroupNamed(gName)
if pGroup then
cfxSSBClient.setSlotAccessForGroup(pGroup)
end
end
function cfxSSBClient.getPlayerGroupForGroupNamed(aName)
local pGroups = cfxSSBClient.playerGroups
for idx, theGroup in pairs(pGroups) do
@ -217,11 +311,10 @@ end
function cfxSSBClient.setSlotAccessByAirfieldOwner()
-- get all groups that have a player-controlled aircraft
-- now uses cached, reduced set of player planes
local pGroups = cfxSSBClient.playerGroups -- cfxGroups.getPlayerGroup() -- we want the group.name attribute
local pGroups = cfxSSBClient.playerGroups
for idx, theGroup in pairs(pGroups) do
cfxSSBClient.setSlotAccessForGroup(theGroup)
end
end
function cfxSSBClient.reOpenSlotForGroupNamed(args)
@ -243,9 +336,6 @@ end
function cfxSSBClient:onEvent(event)
if event.id == 21 then -- S_EVENT_PLAYER_LEAVE_UNIT
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: Player leave unit", 30)
end
local theUnit = event.initiator
if not theUnit then
if cfxSSBClient.verbose then
@ -255,30 +345,38 @@ function cfxSSBClient:onEvent(event)
end
local curH = theUnit:getLife()
local maxH = theUnit:getLife0()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: Health check: " .. curH .. " of " .. maxH, 30)
local uName = theUnit:getName()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: Player leaves unit <" .. uName .. ">", 30)
trigger.action.outText("+++SSB: unit health check: " .. curH .. " of " .. maxH, 30)
end
cfxSSBClient.occupiedUnits[uName] = nil -- forget I was occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
return
end
if event.id == 10 then -- S_EVENT_BASE_CAPTURED
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: CAPTURE EVENT -- RESETTING SLOTS", 30)
if cfxSSBClient.verbose then
local place = event.place
trigger.action.outText("+++SSB: CAPTURE EVENT: <" .. place:getName() .. "> now owned by <" .. place:getCoalition() .. "> -- RESETTING SLOTS", 30)
end
cfxSSBClient.setSlotAccessByAirfieldOwner()
end
-- write down player names and planes
if event.id == 15 then
--trigger.action.outText("+++SSBC:SU: enter event 15", 30)
if event.id == 15 then -- birth
if not event.initiator then return end
local theUnit = event.initiator -- we know this exists
local uName = theUnit:getName()
if not uName then return end
-- player entered unit?
-- check if this is cloned impostor
-- check if this is a cloned impostor
if not theUnit.getPlayerName then
trigger.action.outText("+++SSBC: non-player 'client' " .. uName .. " detected, ignoring.", 30)
if cfxSSBClient.verbose then
trigger.action.outText("+++SSBC: non-player 'client' " .. uName .. " detected, ignoring.", 30)
end
return
end
local playerName = theUnit:getPlayerName()
@ -291,12 +389,22 @@ function cfxSSBClient:onEvent(event)
if cfxSSBClient.verbose then
trigger.action.outText("+++SSBC:SU: noted " .. playerName .. " piloting player unit " .. uName, 30)
end
-- mark it as occupied to player won't get kicked until they
-- leave the unit
cfxSSBClient.occupiedUnits[uName] = playerName
return
end
if event.id == 5 then -- crash PRE-processing
if not event.initiator then return end
local theUnit = event.initiator
local uName = theUnit:getName()
cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
end
if cfxSSBClient.singleUse and event.id == 5 then -- crash
if not event.initiator then return end
--if not event.initiator then return end
local theUnit = event.initiator
local uName = theUnit:getName()
if not uName then return end
@ -321,7 +429,9 @@ function cfxSSBClient:onEvent(event)
-- remember this plane to not re-enable if
-- airfield changes hands later
cfxSSBClient.crashedGroups[gName] = thePilot -- set to crash pilot
trigger.action.outText("+++SSBC:SU: Blocked slot for group <" .. gName .. ">", 30)
if cfxSSBClient.verbose then
trigger.action.outText("+++SSBC:SU: Blocked slot for group <" .. gName .. ">", 30)
end
if cfxSSBClient.reUseAfter > 0 then
-- schedule re-opening this slot in <x> seconds
@ -340,8 +450,38 @@ function cfxSSBClient.update()
-- now establish all slot blocks
cfxSSBClient.setSlotAccessByAirfieldOwner()
-- show occupied planes
if cfxSSBClient.verbose then
for uName, pName in pairs (cfxSSBClient.occupiedUnits) do
trigger.action.outText("+++ssbc: <" .. uName .. "> occupied by <" .. pName .. ">", 30)
end
end
end
function cfxSSBClient.dmlUpdate()
-- first, re-schedule me in one second
timer.scheduleFunction(cfxSSBClient.dmlUpdate, {}, timer.getTime() + 1)
for idx, theZone in pairs (cfxSSBClient.clientZones) do
-- see if we received any signals on out inputs
if theZone.ssbOpen and cfxZones.testZoneFlag(theZone, theZone.ssbOpen, theZone.ssbTriggerMethod, "lastSsbOpen") then
if theZone.verbose then
trigger.action.outText("+++ssbc: <" .. theZone.name .. "> open input triggered for <" .. theZone.afName .. ">", 30)
end
cfxSSBClient.openAirFieldNamed(theZone.afName)
end
if theZone.ssbClose and cfxZones.testZoneFlag(theZone, theZone.ssbClose, theZone.ssbTriggerMethod, "lastSsbClose") then
if theZone.verbose then
trigger.action.outText("+++ssbc: <" .. theZone.name .. "> close input triggered for <" .. theZone.afName .. ">", 30)
end
cfxSSBClient.closeAirfieldNamed(theZone.afName)
end
end
end
-- pre-process static player data to minimize
-- processor load on checks
function cfxSSBClient.processPlayerData()
@ -376,7 +516,7 @@ function cfxSSBClient.processGroupData()
theAirfield, delta = cfxSSBClient.getClosestAirbaseTo(playerData.point)
local afName = theAirfield:getName()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " closest to AF " .. afName .. ": " .. delta .. "m" , 30)
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " closest to AF " .. afName .. ": " .. math.floor(delta) .. "m" , 30)
end
if delta > cfxSSBClient.maxAirfieldRange then
-- forget airfield
@ -437,8 +577,10 @@ function cfxSSBClient.saveData()
local theData = {}
local states = dcsCommon.clone(cfxSSBClient.slotState)
local crashed = dcsCommon.clone(cfxSSBClient.crashedGroups)
local closed = dcsCommon.clone(cfxSSBClient.closedAirfields)
theData.states = states
theData.crashed = crashed
theData.closed = closed
return theData
end
@ -463,12 +605,17 @@ function cfxSSBClient.loadData()
trigger.action.outText("SSB: blocked <" .. slot .. "> on load", 30)
end
end
cfxSSBClient.crashedGroups = theData.crashed
if not cfxSSBClient.crashedGroups then
cfxSSBClient.crashedGroups = {}
trigger.action.outText("SSBClient: nil crashers on load", 30)
end
if theData.crashed then
cfxSSBClient.crashedGroups = theData.crashed
if not cfxSSBClient.crashedGroups then
cfxSSBClient.crashedGroups = {}
trigger.action.outText("SSBClient: nil crashers on load", 30)
end
end
if theData.closed then
cfxSSBClient.closedAirfields = theData.closed
end
end
@ -495,9 +642,20 @@ function cfxSSBClient.start()
-- into cfxSSBClient.playerGroups
cfxSSBClient.processPlayerData()
-- process ssbc zones
-- for in-mission DML interface
local attrZones = cfxZones.getZonesWithAttributeNamed("ssbClient")
for k, theZone in pairs(attrZones) do
cfxSSBClient.createClientZone(theZone) -- process attributes
cfxSSBClient.addClientZone(theZone) -- add to list
end
-- install a timed update just to make sure
-- and start NOW
timer.scheduleFunction(cfxSSBClient.update, {}, timer.getTime() + 1)
-- start dml update (on a different timer
cfxSSBClient.dmlUpdate()
-- now turn on ssb
trigger.action.setUserFlag("SSB",100)
@ -515,7 +673,6 @@ function cfxSSBClient.start()
-- say hi!
trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " running, SBB enabled", 30)
--cfxSSBClient.allYourBase()
return true
end

View File

@ -1,5 +1,5 @@
cfxSmokeZone = {}
cfxSmokeZone.version = "1.1.0"
cfxSmokeZone.version = "1.1.1"
cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -16,16 +16,8 @@ cfxSmokeZone.requiredLibs = {
- alphanum DML flag upgrade
- random color support
1.1.0 - Watchflag upgrade
1.1.1 - stopSmoke? input
SMOKE ZONES *** EXTENDS ZONES ***
keeps 'eternal' smoke up for any zone that has the
'smoke' attribute
USAGE
add a 'smoke' attribute to the zone. the value of the attribute
defines the color. Valid values are: red, green, blue, white, orange, 0 (results in green smoke), 1 (red smoke), 2 (white), 3 (orange), 4 (blue)
defaults to "green"
altiude is meters above ground height, defaults to 5m
--]]--
cfxSmokeZone.smokeZones = {}
cfxSmokeZone.updateDelay = 5 * 60 -- every 5 minutes
@ -51,9 +43,7 @@ function cfxSmokeZone.processSmokeZone(aZone)
-- f? query flags
if cfxZones.hasProperty(aZone, "f?") then
aZone.onFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "*<none>")
end
if cfxZones.hasProperty(aZone, "startSmoke?") then
elseif cfxZones.hasProperty(aZone, "startSmoke?") then
aZone.onFlag = cfxZones.getStringFromZoneProperty(aZone, "startSmoke?", "none")
end
@ -61,6 +51,11 @@ function cfxSmokeZone.processSmokeZone(aZone)
aZone.onFlagVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- save last value
end
if cfxZones.hasProperty(aZone, "stopSmoke?") then
aZone.smkStopFlag = cfxZones.getStringFromZoneProperty(aZone, "stopSmoke?", "<none>")
aZone.smkLastStopFlag = cfxZones.getFlagValue(aZone.smkStopFlag, aZone)
end
-- watchflags:
-- triggerMethod
aZone.smokeTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
@ -145,16 +140,13 @@ function cfxSmokeZone.checkFlags()
-- see if this changed
if cfxZones.testZoneFlag(aZone, aZone.onFlag, aZone.smokeTriggerMethod, "onFlagVal") then
cfxSmokeZone.startSmoke(aZone)
end
--[[--
-- old code
local currTriggerVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- trigger.misc.getUserFlag(aZone.onFlag)
if currTriggerVal ~= aZone.onFlagVal then
-- yupp, trigger start
cfxSmokeZone.startSmoke(aZone)
aZone.onFlagVal = currTriggerVal
end
--]]--
end
end
if aZone.smkStopFlag then
if cfxZones.testZoneFlag(aZone, aZone.smkStopFlag, aZone.smokeTriggerMethod, "smkLastStopFlag") then
aZone.paused = true -- will no longer re-smoke on update
end
end
end
end

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.7.5"
dcsCommon.version = "2.7.6"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -104,6 +104,7 @@ dcsCommon.version = "2.7.5"
2.7.5 - new bitAND32()
- new LSR()
- new num2bin()
2.7.6 - new getObjectsForCatAtPointWithRadius()
--]]--
@ -2693,6 +2694,25 @@ function dcsCommon.objectHandler(theObject, theCollector)
return true
end
function dcsCommon.getObjectsForCatAtPointWithRadius(aCat, thePoint, theRadius)
if not aCat then aCat = Object.Category.UNIT end
local p = {x=thePoint.x, y=thePoint.y, z=thePoint.z}
local collector = {}
-- now build the search argument
local args = {
id = world.VolumeType.SPHERE,
params = {
point = p,
radius = theRadius
}
}
-- now call search
world.searchObjects(aCat, args, dcsCommon.objectHandler, collector)
return collector
end
function dcsCommon.getSceneryObjectsInZone(theZone) -- DCS ZONE!!!
local aCat = 5 -- scenery
-- WARNING: WE ARE USING DCS ZONES, NOT CFX!!!

View File

@ -1,5 +1,5 @@
persistence = {}
persistence.version = "1.0.3"
persistence.version = "1.0.4"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@ -24,10 +24,11 @@ persistence.requiredLibs = {
1.0.2 - QoL when verbosity is on
1.0.3 - no longer always tells " mission saved to"
new 'saveNotification" can be off
1.0.4 - new optional 'root' property
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIOBILITY
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
--]]--
@ -255,7 +256,6 @@ function persistence.initFlagsFromData(theFlags)
trigger.action.outText("+++persistence: no flags loaded, commencing mission data load", 30)
end
end
function persistence.missionStartDataLoad()
@ -427,7 +427,24 @@ function persistence.readConfigZone()
end
-- serverDir is the path from the server save directory, usually "Missions/".
-- will be added to lfs.writedir().
-- will be added to lfs.writedir() unless given a root attribute
if cfxZones.hasProperty(theZone, "root") then
-- we split this to enable further processing down the
-- line if neccessary
persistence.root = cfxZones.getStringFromZoneProperty(theZone, "root", lfs.writedir()) -- safe default
if not dcsCommon.stringEndsWith(persistence.root, "\\") then
persistence.root = persistence.root .. "\\"
end
if theZone.verbose then
trigger.action.outText("+++persistence: setting root to <" .. persistence.root .. ">", 30)
end
else
persistence.root = lfs.writedir() -- safe defaulting
if theZone.verbose then
trigger.action.outText("+++persistence: defaulting root to <" .. persistence.root .. ">", 30)
end
end
persistence.serverDir = cfxZones.getStringFromZoneProperty(theZone, "serverDir", "Missions\\")
if hasConfig then
@ -509,13 +526,13 @@ function persistence.start()
end
end
local mainDir = lfs.writedir() .. persistence.serverDir
-- local mainDir = lfs.writedir() .. persistence.serverDir
local mainDir = persistence.root .. persistence.serverDir
if not dcsCommon.stringEndsWith(mainDir, "\\") then
mainDir = mainDir .. "\\"
end
-- lets see if we can access the server's mission directory and
-- save directory
-- we first try to access server's main mission directory, called "mainDir" which is usually <writeDir>/Missions/>
if persistence.isDir(mainDir) then
if persistence.verbose then
@ -563,6 +580,7 @@ function persistence.start()
end
end
-- missionDir is root + serverDir + saveDir
persistence.missionDir = missionDir
persistence.active = true -- we can load and save data

View File

@ -1,7 +1,8 @@
williePete = {}
williePete.version = "1.0.0"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- mach two missile is within 33 meters, with interpolation even less
-- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters
williePete.requiredLibs = {
"dcsCommon", -- always
@ -12,8 +13,11 @@ williePete.requiredLibs = {
williePete.willies = {}
williePete.wpZones = {}
williePete.playerGUIs = {} -- used for unit guis
williePete.blastedObjects = {} -- used when we detonate something
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM"}
-- 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"}
function williePete.addWillie(theWillie)
table.insert(williePete.willies, theWillie)
@ -72,8 +76,6 @@ function williePete.getClosestZoneForCoa(point, coa)
currDelta = delta
closestZone = zData
end
else
-- trigger.outText("Zone <" .. zData.name .. ">, coa <" .. zData.coalition .. "> does not match <" .. coa .. ">", 30)
end
end
return closestZone, currDelta
@ -91,19 +93,20 @@ function williePete.createWPZone(aZone)
aZone.readyTime = 0 -- if readyTime > now we are not ready
aZone.trackingPlayer = nil -- name player's unit who is being tracked for wp. may not be neccessary
aZone.checkedIn = {} -- dict of all planes currently checked in
aZone.trackingEndsTime = 0 -- if now > trackingends, we remove player and send a message
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "wpTriggerMethod", "change")
aZone.FACTypes = cfxZones.getStringFromZoneProperty(aZone, "facTypes", "all")
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "wpMethod", "change")
aZone.checkInRange = cfxZones.getNumberFromZoneProperty(aZone, "checkInRange", williePete.checkInRange) -- default to my default
aZone.ackSound = cfxZones.getStringFromZoneProperty(aZone, "ackSound", williePete.ackSound)
aZone.guiSound = cfxZones.getStringFromZoneProperty(aZone, "guiSound", williePete.guiSound)
if cfxZones.hasProperty(aZone, "triggerMethod") then
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
if cfxZones.hasProperty(aZone, "method") then
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "change")
end
if cfxZones.hasProperty(aZone, "wpFire!") then
aZone.wpFire = cfxZones.getStringFromZoneProperty(aZone, "wpFire!", "<none)")
end
if aZone.verbose then
@ -112,7 +115,7 @@ function williePete.createWPZone(aZone)
end
--
-- player management
-- PLAYER MANAGEMENT
--
function williePete.startPlayerGUI()
-- scan all mx players
@ -158,22 +161,33 @@ function williePete.startPlayerGUI()
end
end
--
-- BOOM
--
--
-- BOOM command
--
function williePete.doBoom(args)
local unitInfo = args.unitInfo
if unitInfo then
-- note that unit who commânded fire may no longer be alive
-- so check it every time. unit must be alive
-- to receive credits later
local uName = unitInfo.name
local blastRad = math.floor(math.sqrt(args.strength)) * 2
if blastRad < 10 then blastRad = 10 end
local affectedUnits = dcsCommon.getObjectsForCatAtPointWithRadius(nil, args.point, blastRad)
for idx, aUnit in pairs(affectedUnits) do
local aName = aUnit:getName()
if williePete.verbose then
trigger.action.outText("<" .. aName .. "> is in blast Radius (" .. blastRad .. "m) of shells for <" .. uName .. ">'s target coords", 30)
end
williePete.blastedObjects[aName] = uName -- last one gets the kill
end
end
trigger.action.explosion(args.point, args.strength)
data = {}
data.point = args.point
data.strength = args.strength
-- cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
end
function williePete.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime)
function williePete.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime, unitInfo)
if williePete.verbose then
trigger.action.outText("fire with accuracy <" .. accuracy .. "> shellNum <" .. shellNum .. "> baseStren <" .. shellBaseStrength .. "> variance <" .. shellVariance .. ">, ttime <" .. transitionTime .. ">", 30)
end
@ -200,6 +214,7 @@ function williePete.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStre
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
boomArgs.point = thePoint
boomArgs.zone = aZone
boomArgs.unitInfo = unitInfo
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
if timeVar < 0 then timeVar = -timeVar end
@ -218,6 +233,12 @@ end
function williePete.doCheckIn(unitInfo)
--trigger.action.outText("check-in received", 30)
local theUnit = Unit.getByName(unitInfo.name)
if not theUnit then
-- dead man calling. Pilot dead but unit still alive
trigger.action.outText("Calling station, say again, can't read you.", 30)
return
end
local p = theUnit:getPoint()
local theZone, dist = williePete.closestCheckInTgtZoneForCoa(p, unitInfo.coa)
@ -245,12 +266,12 @@ function williePete.doCheckIn(unitInfo)
theZone.checkedIn[unitInfo.name] = unitInfo
-- add the 'Target marked' menu
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked, commence firing", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
-- remove 'check in'
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.checkIn)
unitInfo.checkIn = nil
-- add 'check out'
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out", unitInfo.root, williePete.redirectCheckOut, unitInfo)
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out of " .. theZone.name, unitInfo.root, williePete.redirectCheckOut, unitInfo)
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.name .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
@ -261,8 +282,6 @@ function williePete.redirectCheckOut(unitInfo)
end
function williePete.doCheckOut(unitInfo)
--trigger.action.outText("check-out received", 30)
-- check out of all zones
local wasCheckedIn = false
local fromZone = ""
@ -305,10 +324,15 @@ function williePete.rogerDodger(args)
end
function williePete.doTargetMarked(unitInfo)
--trigger.action.outText("mark received", 30)
-- first, check if we are past the time-out
local now = timer.getTime()
if not unitInfo.wpInZone then
trigger.action.outTextForGroup(unitInfo.gID, "No target mark visible, please mark again", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
-- now check if zone matches check-in
if not unitInfo.expiryTime or unitInfo.expiryTime < now then
trigger.action.outTextForGroup(unitInfo.gID, "Target mark stale or ambiguous, set fresh mark", 30)
@ -357,13 +381,15 @@ function williePete.doTargetMarked(unitInfo)
local transitionTime = tgtZone.transitionTime
local accuracy = tgtZone.baseAccuracy
williePete.doParametricFireAt(unitInfo.pos, accuracy, shellNum, shellStrength, 0.2, transitionTime)
williePete.doParametricFireAt(unitInfo.pos, accuracy, shellNum, shellStrength, 0.2, transitionTime, unitInfo)
-- set zone's cooldown
tgtZone.readyTime = now + tgtZone.coolDown
-- erase player's wp mark
unitInfo.wpInZone = nil
unitInfo.pos = nil
-- if we have an output, trigger it now
if tgtZone.wpFire then
cfxZones.pollFlag(tgtZone.wpFire, tgtZone.wpMethod, tgtZone)
end
end
-- return true if a zone is actively tracking theUnit to place
-- a wp
@ -381,16 +407,43 @@ function williePete.isWP(theWeapon)
for idx, wpw in pairs(williePete.smokeWeapons) do
if theDesc == wpw then return true end
end
trigger.action.outText(theDesc .. " is no wp, ignoring.", 30)
return false
end
function williePete.zedsDead(theObject)
if not theObject then return end
local theName = theObject:getName()
-- now check if it's a registered blasted object:getSampleRate()
local blaster = williePete.blastedObjects[theName]
if blaster then
local theUnit = Unit.getByName(blaster)
if theUnit then
-- interface to playerscore
if cfxPlayerScore then
local fakeEvent = {}
fakeEvent.initiator = theUnit -- killer
fakeEvent.target = theObject -- vic
cfxPlayerScore.killDetected(fakeEvent)
end
end
williePete.blastedObjects[theName] = nil
end
end
function williePete:onEvent(event)
if not event.initiator then
--trigger.action.outText("onEvent - " .. event.id .. ": no initiator",30)
return
end
-- check if it's a dead event
if event.id == 8 then
-- death event
williePete.zedsDead(event.initiator)
end
if not event.weapon then
--trigger.action.outText("onEvent - " .. event.id .. ": no WEAPON",30)
return
end
@ -401,21 +454,18 @@ function williePete:onEvent(event)
if event.id == 1 then -- S_EVENT_SHOT
-- initiator is who fired. maybe want to test if player
--trigger.action.outText(theUnit:getName() .. " " .. pType .. " fired " .. event.weapon:getTypeName() .. ".", 30)
if not williePete.isWP(event.weapon) then
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> not a smoke weapon", 30)
-- we only trigger on WP weapons
return
end
-- make sure that whoever fired it is being tracked by
-- a zone
if not williePete.zoneIsTracking(theUnit) then
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> fired while not being tracked by zone", 30)
return
end
-- assuming it's a willie, let's track it
-- it's a willie, fired by player who is checked in: let's track it
local theWillie = {}
theWillie.firedBy = theUnit:getName()
theWillie.theUnit = theUnit
@ -427,16 +477,18 @@ function williePete:onEvent(event)
williePete.addWillie(theWillie)
end
--[[--
if event.id == 2 then -- hit
local what = "something"
if event.target then what = event.target:getName() end
--trigger.action.outText("Weapon " .. event.weapon:getTypeName() .. " fired by unit ".. theUnit:getName() .. " " .. pType .. " hit " .. what, 30)
-- may need to remove willie from willies
end
--]]--
end
-- test if a projectile hit ground inside a wp zone
-- test if a projectile has hit the ground inside a wp zone
function williePete.isInside(theWillie)
local thePoint = theWillie.pos
local theUnitName = theWillie.firedBy -- may be dead already, but who cares
@ -457,8 +509,6 @@ function williePete.isInside(theWillie)
end
-- if we want to allow neutral zones (doens't make sense)
-- add another guard below
else
--trigger.action.outText("willie outside " .. theZone.name, 30)
end
end
return nil
@ -471,7 +521,6 @@ function williePete.projectileHit(theWillie)
-- interpolate pos: half time between updates times last velocity
local vmod = dcsCommon.vMultScalar(theWillie.v, 0.5 / williePete.ups)
theWillie.pos = dcsCommon.vAdd(theWillie.pos, vmod)
--trigger.action.outText("Willie " .. theWillie.wt .. " expired at " .. dcsCommon.point2text(theWillie.pos) .. " interpolated.", 30)
-- reset last mark for player
local thePlayer = williePete.playerGUIs[theWillie.firedBy]
@ -493,15 +542,6 @@ function williePete.projectileHit(theWillie)
thePlayer.pos = theWillie.pos -- remember the loc
thePlayer.wpInZone = theZone -- remember the zone
-- mark point with smoke blue
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
if cfxArtilleryZones then
--cfxArtilleryZones.doParametricFireAt(theWillie.pos, 50, 10)
else
-- mark point with smoke blue
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
end
end
function williePete.updateWP()
@ -635,14 +675,3 @@ if not williePete.start() then
trigger.action.outText("cf/x Willie Pete aborted: missing libraries", 30)
williePete = nil
end
--[[--
Mechanics:
- unit checks in with arty zone. if not in range of arty zone + safe dist, error 'not in range' is returned, else <artillery zone: status> is sent. <status can be ready, firing, or reloading>. Zone will advise on status change when checked in.
- if unit leaves arty zone + safe dist, <leaving zone> is displayed and <checkout> is invoked
- unit can check out any time
- when checked in, any wp hitting the ground is remembered if still inside target zone
- player then gives 'target marked'
- if artillery on cooldown, or wp not inside zone error, else fire sequence starts, and cooldown starts for entire zone
--]]--

View File

@ -1,5 +1,5 @@
xFlags = {}
xFlags.version = "1.3.0"
xFlags.version = "1.3.1"
xFlags.verbose = false
xFlags.hiVerbose = false
xFlags.ups = 1 -- overwritten in get config when configZone is present
@ -27,7 +27,13 @@ xFlags.requiredLibs = {
- hiVerbose option
- corrected bug in reset checksum
1.3.0 - xCount! flag
- "never" operator
- "never" operator
1.3.1 - guards for xtriggerOffFlag and xtriggerOnFlag
to prevent QoL warnings
- guards for xDirect
- guards for xCount
--]]--
xFlags.xFlagZones = {}
@ -96,9 +102,13 @@ function xFlags.createXFlagsWithZone(theZone)
theZone.xChange = cfxZones.getStringFromZoneProperty(theZone, "xChange!", "*<none>")
end
theZone.xDirect = cfxZones.getStringFromZoneProperty(theZone, "xDirect", "*<none>")
if cfxZones.hasProperty(theZone, "xDirect") then
theZone.xDirect = cfxZones.getStringFromZoneProperty(theZone, "xDirect", "*<none>")
end
theZone.xCount = cfxZones.getStringFromZoneProperty(theZone, "xCount", "*<none>")
if cfxZones.hasProperty(theZone, "xCount") then
theZone.xCount = cfxZones.getStringFromZoneProperty(theZone, "xCount", "*<none>")
end
theZone.inspect = cfxZones.getStringFromZoneProperty(theZone, "require", "or") -- same as any
-- supported any/or, all/and, moreThan, atLeast, exactly
@ -132,11 +142,14 @@ function xFlags.createXFlagsWithZone(theZone)
trigger.action.outText("+++xFlg: <" .. theZone.name .. "> starts suspended", 30)
end
theZone.xtriggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "xOn?", "*<none1>")
theZone.xlastTriggerOnValue = cfxZones.getFlagValue(theZone.xtriggerOnFlag, theZone)
theZone.xtriggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "xOff?", "*<none2>")
theZone.xlastTriggerOffValue = cfxZones.getFlagValue(theZone.xtriggerOffFlag, theZone)
if cfxZones.hasProperty(theZone, "xOn?") then
theZone.xtriggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "xOn?", "*<none1>")
theZone.xlastTriggerOnValue = cfxZones.getFlagValue(theZone.xtriggerOnFlag, theZone)
end
if cfxZones.hasProperty(theZone, "xOff?") then
theZone.xtriggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "xOff?", "*<none2>")
theZone.xlastTriggerOffValue = cfxZones.getFlagValue(theZone.xtriggerOffFlag, theZone)
end
end
function xFlags.evaluateNumOrFlag(theAttribute, theZone)
@ -363,15 +376,18 @@ function xFlags.evaluateZone(theZone)
-- now directly set the value of evalResult (0 = false, 1 = true)
-- to "xDirect". Always sets output to current result of evaluation
-- true (1)/false(0), no matter if changed or not
if evalResult then
cfxZones.setFlagValueMult(theZone.xDirect, 1, theZone)
else
cfxZones.setFlagValueMult(theZone.xDirect, 0, theZone)
end
if theZone.xDirect then
if evalResult then
cfxZones.setFlagValueMult(theZone.xDirect, 1, theZone)
else
cfxZones.setFlagValueMult(theZone.xDirect, 0, theZone)
end
end
-- directly set the xCount flag
cfxZones.setFlagValueMult(theZone.xCount, hits, theZone)
if theZone.xCount then
cfxZones.setFlagValueMult(theZone.xCount, hits, theZone)
end
-- now see if we bang the output according to method
if evalResult then
@ -391,14 +407,14 @@ function xFlags.update()
for idx, theZone in pairs (xFlags.xFlagZones) do
-- see if we should suspend
if cfxZones.testZoneFlag(theZone, theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
if theZone.xtriggerOnFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
if xFlags.verbose or theZone.verbose then
trigger.action.outText("+++xFlg: enabling " .. theZone.name, 30)
end
theZone.xSuspended = false
end
if cfxZones.testZoneFlag(theZone, theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
if theZone.xtriggerOffFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
if xFlags.verbose or theZone.verbose then
trigger.action.outText("+++xFlg: DISabling " .. theZone.name, 30)
end

Binary file not shown.

Binary file not shown.