diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index fff7e3e..033ee5b 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf index 8aae624..8070df9 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxSSBClient.lua b/modules/cfxSSBClient.lua index 644ac0b..6548e3b 100644 --- a/modules/cfxSSBClient.lua +++ b/modules/cfxSSBClient.lua @@ -1,5 +1,5 @@ cfxSSBClient = {} -cfxSSBClient.version = "3.0.0" +cfxSSBClient.version = "3.0.1" cfxSSBClient.verbose = false cfxSSBClient.singleUse = false -- set to true to block crashed planes -- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick @@ -47,6 +47,7 @@ Version History - open? - close? - also persists closed airfield list + 3.0.1 - ability to detect if an airfield doesn't exist (late activate) @@ -238,38 +239,46 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup) -- and leave it as it is. Nothing to do at all now elseif theMatchingAirfield ~= nil then + local blockState = cfxSSBClient.enabledFlagValue -- we default to ALLOW the block + local comment = "available" -- we have found a plane that is tied to an airfield -- so this group will receive a block/unblock -- we always set all block/unblock every time - -- note: since caching, above guard not needed - local airFieldSide = theMatchingAirfield:getCoalition() - local groupCoalition = theGroup.coaNum - local blockState = cfxSSBClient.enabledFlagValue -- we default to ALLOW the block - local comment = "available" - - -- see if airfield is closed - local afName = theMatchingAirfield:getName() - if cfxSSBClient.closedAirfields[afName] then - -- airfield is closed. no take-offs + + -- see if airfield currently exist (might be dead or late activate) + if not Object.isExist(theMatchingAirfield) then + -- airfield does not exits yet/any more blockState = cfxSSBClient.disabledFlagValue - comment = "!closed airfield!" - end + comment = "!inactive airfield!" + else + local airFieldSide = theMatchingAirfield:getCoalition() + local groupCoalition = theGroup.coaNum - -- on top of that, check coalitions - if groupCoalition ~= airFieldSide then - -- we have a problem. sides don't match - if airFieldSide == 3 - or (cfxSSBClient.allowNeutralFields and airFieldSide == 0) - then - -- all is well, airfield is contested or neutral and - -- we allow this plane to spawn here - else - -- DISALLOWED!!!! + -- see if airfield is closed + local afName = theMatchingAirfield:getName() + if cfxSSBClient.closedAirfields[afName] then + -- airfield is closed. no take-offs blockState = cfxSSBClient.disabledFlagValue - comment = "!!!BLOCKED!!!" + comment = "!closed airfield!" end + + -- on top of that, check coalitions + if groupCoalition ~= airFieldSide then + -- we have a problem. sides don't match + if airFieldSide == 3 + or (cfxSSBClient.allowNeutralFields and airFieldSide == 0) + then + -- all is well, airfield is contested or neutral and + -- we allow this plane to spawn here + else + -- DISALLOWED!!!! + blockState = cfxSSBClient.disabledFlagValue + comment = "!!!BLOCKED!!!" + end + end end - -- set the ssb flag for this group so the server can see it + + -- now 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 diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index 3d1061f..2b4f331 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -1,5 +1,5 @@ csarManager = {} -csarManager.version = "2.2.3" +csarManager.version = "2.2.4" csarManager.verbose = false csarManager.ups = 1 @@ -60,6 +60,8 @@ csarManager.ups = 1 - 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 INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED @@ -1273,18 +1275,18 @@ end function csarManager.readCSARZone(theZone) -- zones have attribute "CSAR" -- gather data, and then create a mission from this + local mName = cfxZones.getStringFromZoneProperty(theZone, "CSAR", "Lt. Unknown") + if mName == "" then mName = theZone.name end local theSide = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) theZone.csarSide = theSide - theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "name", "") - theZone.csarName = theZone.name -- default CSAR Name - if cfxZones.hasProperty(theZone, "csarName") then + theZone.csarName = mName -- now deprecating name attributes + if cfxZones.hasProperty(theZone, "name") then + theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "name", "") + elseif cfxZones.hasProperty(theZone, "csarName") then theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "csarName", "") - end - if cfxZones.hasProperty(theZone, "pilotName") then + elseif cfxZones.hasProperty(theZone, "pilotName") then theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "pilotName", "") - end - - if cfxZones.hasProperty(theZone, "victimName") then + elseif cfxZones.hasProperty(theZone, "victimName") then theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "victimName", "") end @@ -1302,6 +1304,11 @@ function csarManager.readCSARZone(theZone) theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) end + if cfxZones.hasProperty(theZone, "start?") then + theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "start?", "*none") + theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) + end + if cfxZones.hasProperty(theZone, "startCSAR?") then theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "startCSAR?", "*none") theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) @@ -1479,29 +1486,18 @@ end improvements - need to stay on ground for x seconds to load troops - hot lz - - hover recover - limit on troops aboard for transport - delay for drop-off - - - csar when: always, only on eject, - + - repair o'clock - - - nearest csarBase - - red/blue csarbases - - weight - + - compatibility: side/owner - make sure it is compatible with FARP, and landing on a FARP with opposition ownership will not disembark - - - suppress multi smoke - + - when unloading one by menu, update weight!!! + + -- allow any airfied to be csarsafe by default, no longer *requires* csarbase - -- allow neutral pick-up - - -- allow any airfied to be csarsafe by default, no longer requires csarbase - -- get vector to closes csarbase - + -- support quad zones and optionally non-random placement --]]-- \ No newline at end of file diff --git a/modules/delicates.lua b/modules/delicates.lua index 9f8d1bb..c262bbb 100644 --- a/modules/delicates.lua +++ b/modules/delicates.lua @@ -1,5 +1,5 @@ delicates = {} -delicates.version = "1.1.1" +delicates.version = "1.1.2" delicates.verbose = false delicates.ups = 1 delicates.requiredLibs = { @@ -18,7 +18,7 @@ delicates.inventory = {} - safetyMargin - safety margin. defaults to 10% 1.1.1 - addGroupToInventoryForZone - verbose for zone will show update event from useDelicates - + 1.1.2 - fixed uncaught delayed explosion for nil object --]]-- function delicates.adddDelicates(theZone) table.insert(delicates.theDelicates, theZone) @@ -194,8 +194,21 @@ function delicates.scheduledBlow(args) theObject = StaticObject.getByName(oName) else -- can't handle at the moment + if delicates.verbose then + trigger.action.outText("+++Deli: can't handle delicate explosion for object cat <" .. desc.cat .. ">, name <" .. desc.oName .. ">", 30) + end + return end + -- now, the object might have been removed by now. catch this before we proceed + if not theObject then + if delicates.verbose then + trigger.action.outText("+++Deli: NIL delicate for object blow at cat <" .. desc.cat .. ">, name <" .. desc.oName .. ">", 30) + end + return + end + + local theZone = desc.theZone local p = theObject:getPoint() local power = desc.theZone.power diff --git a/modules/taxiPolice.lua b/modules/taxiPolice.lua index 7f74593..79bfeff 100644 --- a/modules/taxiPolice.lua +++ b/modules/taxiPolice.lua @@ -1,5 +1,5 @@ taxiPolice = {} -taxiPolice.version = "0.0.0" +taxiPolice.version = "1.0.0" taxiPolice.verbose = true taxiPolice.ups = 1 -- checks per second taxiPolice.requiredLibs = { @@ -7,32 +7,32 @@ taxiPolice.requiredLibs = { "cfxZones", -- Zones, of course } --[[-- --- ensure that a player doesn't overspeed on taxiways. uses speedLimit and violateDuration to determine if to fine - --- create runway polys here: https://wiki.hoggitworld.com/view/DCS_func_getRunways - --- works as follows: --- when a player's plane is not inAir, they are monitored. --- when on a runway or too far from airfield (only airfields are monitored) monitoring ends --- when monitored and overspeeding, they first receive a warning, and after n warnings they receive retribution + Version History + 1.0.0 - Initial version + --]]-- + taxiPolice.speedLimit = 14 -- m/s . 14 m/s = 50 km/h, 10 m/s = 36 kmh taxiPolice.triggerTime = 3 -- seconds until we register a speeding violation taxiPolice.rwyLeeway = 5 -- meters on each side taxiPolice.rwyExtend = 500 -- meters in front and at end taxiPolice.airfieldMaxDist = 3000 -- radius around airfield in which we operate taxiPolice.runways = {} -- indexed by airbase name, then by rwName + -- if nil, that base is not policed taxiPolice.suspects = {} -- units that are currently behaving naughty taxiPolice.tickets = {} -- number of warnings per player taxiPolice.maxTickets = 3 -- number of tickes without retribution - +taxiPolice.lastMessageTo = {} -- used to suppress messages if too soon function taxiPolice.buildRunways() local bases = world.getAirbases() local mId = 0 for idb, aBase in pairs (bases) do -- i = 1, #base do local name = aBase:getName() local rny = aBase:getRunways() - if rny then + -- Note that Airbase.Category values are not obtained by calling airbase:getCategory() - that calls Object.getCategory(airbase) and will always return the value Object.Category.BASE. Instead you need to use airbase:getDesc().category to obtain the Airbase.Category! + local cat = aBase:getDesc().category + + if rny and (cat == 0) then local runways = {} for idx, rwy in pairs(rny) do -- j = 1, #rny do -- calcualte quad that encloses taxiway @@ -71,6 +71,7 @@ function taxiPolice.buildRunways() taxiPolice.runways[name] = runways if taxiPolice.verbose then + -- mark center of airbase in a red 200x200 square local points = {} local pStart = aBase:getPoint() local pEnd = aBase:getPoint() @@ -85,6 +86,10 @@ function taxiPolice.buildRunways() mId = mId + 1 trigger.action.quadToAll(-1, mId, points[1], points[2], points[3], points[4], {1, 0, 0, 1}, {1, 0, 0, .5}, 3) end + else + if taxiPolice.verbose then + trigger.action.outText("No runways proccing for base <" .. name .. ">, cat = <" .. cat .. ">", 30) + end end end end @@ -108,6 +113,10 @@ end function taxiPolice.checkUnit(theUnit, allAirfields) if not theUnit.getPlayerName then return end + local theGroup = theUnit:getGroup() + local cat = theGroup:getCategory() + if cat ~= 0 then return end -- not a fixed wing, disregard + local player = theUnit:getPlayerName() if not player then return end @@ -131,14 +140,13 @@ function taxiPolice.checkUnit(theUnit, allAirfields) -- check if we are on a runway local myRunways = taxiPolice.runways[base:getName()] if not myRunways then - -- this base is turned off - --trigger.action.outText("unable to find raunways for <" .. base:getName() .. ">", 30) + -- this base is not policed + taxiPolice.suspects[player] = nil return end for rwName, aRunway in pairs(myRunways) do if cfxZones.isPointInsidePoly(p, aRunway) then - --trigger.action.outText("<" .. theUnit:getName() .. "> is on RWY <" .. rwName .. ">", 30) taxiPolice.suspects[player] = nil -- remove watched status return end @@ -184,10 +192,29 @@ end --- --- UPDATE --- -function taxiPolice.update() -- every second + +function taxiPolice.update() -- every second/ups -- schedule next invocation timer.scheduleFunction(taxiPolice.update, {}, timer.getTime() + 1/taxiPolice.ups) + --trigger.action.outText("onpatrol flag is " .. taxiPolice.onPatrol .. " with val = " .. trigger.misc.getUserFlag(taxiPolice.onPatrol), 30) + -- see if this has been turned on or offDuty + if taxiPolice.onPatrol and + cfxZones.testZoneFlag(taxiPolice, taxiPolice.onPatrol, "change", "lastOnPatrol") then + taxiPolice.active = true + local knots = math.floor(taxiPolice.speedLimit * 1.94384) + local kmh = math.floor(taxiPolice.speedLimit * 3.6) + trigger.action.outText("NOTAM:\ntarmac and taxiway speed limit of " .. knots .. " knots/" .. kmh .. " km/h is enforced on all air fields!", 30) + end + + if taxiPolice.offDuty and + cfxZones.testZoneFlag(taxiPolice, taxiPolice.offDuty, "change", "lastOffDuty") then + taxiPolice.active = false + trigger.action.outText("NOTAM:\ntarmac and taxiway speed limit rescinded. Taxi responsibly!", 30) + end + + if not taxiPolice.active then return end + local allAirfields = dcsCommon.getAirbasesWhoseNameContains("*", 0) -- all fixed bases, no FARP nor ships. Pre-collect -- check all player units @@ -203,6 +230,55 @@ function taxiPolice.update() -- every second end +-- +-- ONEVENT +-- + +function taxiPolice:onEvent(theEvent) + --trigger.action.outText("txP event: <" .. theEvent.id .. ">", 30) + if not taxiPolice.greetings then return end -- no warnings + if not taxiPolice.active then return end -- no policing active + + local ID = theEvent.id + if not ID then return end + if (ID ~= 15) and (ID ~= 4) then return end -- not birth nor land + local theUnit = theEvent.initiator + if not theUnit then return end + if theUnit:inAir() then return end + + -- make sure it's a plane. Helos are ignored + local theGroup = theUnit:getGroup() + local cat = theGroup:getCategory() + if cat ~= 0 then return end + + if not theUnit.getPlayerName then return end + local pName = theUnit:getPlayerName() + if not pName then return end + local base, dist = dcsCommon.getClosestAirbaseTo(theUnit:getPoint(), 0) + local bName = base:getName() + if dist > taxiPolice.airfieldMaxDist then return end + + local UID = theUnit:getID() + -- check if this airfield is exempt + local rwys = taxiPolice.runways[bName] + local now = timer.getTime() + local last = taxiPolice.lastMessageTo[pName] -- remember timestamp + if not last then last = 0 end + local tdiff = now - last + -- make sure palyer receives only one such notice within 15 seconds + -- but always when now = 0 (mission startup) + if (now ~= 0) and (tdiff < 15) then return end -- to soon + taxiPolice.lastMessageTo[pName] = now + if not rwys then + trigger.action.outTextForUnit(UID, "Welcome to " .. bName .. ", " .. pName .. "!\nAlthough a general taxiway speed limit is in effect, it does not apply here.", 30) + return + end + + local knots = math.floor(taxiPolice.speedLimit * 1.94384) + local kmh = math.floor(taxiPolice.speedLimit * 3.6) + trigger.action.outTextForUnit(UID, "Welcome to " .. bName .. ", " .. pName .. "!\nBe advised: a speed limit of " .. knots .. " knots/" .. kmh .. " km/h is enforced on tarmac and taxiways.", 30) +end + -- -- START -- @@ -232,6 +308,8 @@ function taxiPolice.readConfigZone() trigger.action.outText("+++txPol: no config zone!", 30) end end + taxiPolice.name = "taxiPoliceConfig" -- cfxZones compatibility + taxiPolice.verbose = theZone.verbose taxiPolice.speedLimit = cfxZones.getNumberFromZoneProperty(theZone, "speedLimit", 14) -- 14 -- m/s. 14 m/s = 50 km/h, 10 m/s = 36 kmh @@ -240,7 +318,19 @@ function taxiPolice.readConfigZone() taxiPolice.rwyExtend = cfxZones.getNumberFromZoneProperty(theZone, "extend", 500) --500 -- meters in front and at end taxiPolice.airfieldMaxDist = cfxZones.getNumberFromZoneProperty(theZone, "radius", 3000) -- 3000 -- radius around airfield in which we operate taxiPolice.maxTickets = cfxZones.getNumberFromZoneProperty(theZone, "maxTickets", 3) -- 3 - + + taxiPolice.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true) + taxiPolice.greetings = cfxZones.getBoolFromZoneProperty(theZone, "greetings", true) + + if cfxZones.hasProperty(theZone, "onPatrol") then + taxiPolice.onPatrol = cfxZones.getStringFromZoneProperty(theZone, "onPatrol", "") + taxiPolice.lastOnPatrol = cfxZones.getFlagValue(taxiPolice.onPatrol, taxiPolice) + end + + if cfxZones.hasProperty(theZone, "offDuty") then + taxiPolice.offDuty = cfxZones.getStringFromZoneProperty(theZone, "offDuty", "") + taxiPolice.lastOffDuty = cfxZones.getFlagValue(taxiPolice.offDuty, taxiPolice) + end end function taxiPolice.start() @@ -265,6 +355,9 @@ function taxiPolice.start() -- start update taxiPolice.update() + -- install envent handler to greet pilots on airfields + world.addEventHandler(taxiPolice) + -- say hi! trigger.action.outText("cfx taxiPolice v" .. taxiPolice.version .. " started.", 30) return true @@ -276,5 +369,10 @@ if not taxiPolice.start() then taxiPolice = nil end - +--[[-- + Possible improvements + - other sanctions on violations like kick, ban etc + - call nearest airfield for open rwys (needs 'commandForUnit' first + - ability to persist offenders +--]]-- diff --git a/tutorial & demo missions/demo - CSAR of Georgia.miz b/tutorial & demo missions/demo - CSAR of Georgia.miz index d41115e..610984b 100644 Binary files a/tutorial & demo missions/demo - CSAR of Georgia.miz and b/tutorial & demo missions/demo - CSAR of Georgia.miz differ diff --git a/tutorial & demo missions/demo - Taxi Police.miz b/tutorial & demo missions/demo - Taxi Police.miz new file mode 100644 index 0000000..0123de6 Binary files /dev/null and b/tutorial & demo missions/demo - Taxi Police.miz differ