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()
local uName = theUnit:getName()
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: Health check: " .. curH .. " of " .. maxH, 30)
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)
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,11 +605,16 @@ function cfxSSBClient.loadData()
trigger.action.outText("SSB: blocked <" .. slot .. "> on load", 30)
end
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
cfxSSBClient.crashedGroups = theData.crashed
if not cfxSSBClient.crashedGroups then
cfxSSBClient.crashedGroups = {}
trigger.action.outText("SSBClient: nil crashers on load", 30)
if theData.closed then
cfxSSBClient.closedAirfields = theData.closed
end
end
@ -495,10 +642,21 @@ 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")
@ -146,15 +141,12 @@ function cfxSmokeZone.checkFlags()
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
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
@ -28,6 +28,12 @@ xFlags.requiredLibs = {
- corrected bug in reset checksum
1.3.0 - xCount! flag
- "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)
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.