diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index f102daa..0869ef0 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 295811e..6ef3d07 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index c2a1ee6..5c23d6e 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,11 +1,11 @@ cfxZones = {} -cfxZones.version = "4.1.2" +cfxZones.version = "4.2.0" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable -- by scripting. -- --- Copyright (c) 2021 - 2023 by Christian Franz and cf/x AG +-- Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG -- --[[-- VERSION HISTORY @@ -44,6 +44,7 @@ cfxZones.version = "4.1.2" - 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well - 4.1.1 - evalRemainder() updates - 4.1.2 - hash property missing warning +- 4.2.0 - new createRandomPointInPopulatedZone() --]]-- @@ -425,6 +426,95 @@ function dmlZone:createRandomPointInPolyZone(onEdge) return p, dx, dz end +function dmlZone:createRandomPointInPopulatedZone(radius, maxTries) + if not maxTries then maxTries = 20 end + if not radius then radius = 10 end -- meters + local cnt = 0 + local p, dx, dz + repeat + p, dx, dz = self:createRandomPointInZone() -- p is x, 0, z + local hits, collector = cfxZones.objectsInRange(p, radius) + if hits < 1 then return p, dx, dz end + if hits == 1 then + local o = collector[1] + local op = o:getPoint() + d = dcsCommon.distFlat(op, p) +-- trigger.action.outText("singleDist = " .. d, 30) + if d > radius/2 then +-- trigger.action.outText("good enough, will use", 30) + return p, dx, dz + end + end + cnt = cnt + 1 +-- trigger.action.outText(hits .. "hits --> failed try " .. cnt, 30) + until cnt > maxTries + return p, dx, dz +end + +function cfxZones.createRandomPointInPopulatedZone(theZone, radius, maxTries) + if not theZone then return nil, nil, nil end + local p, dx, dz = theZone:createRandomPointInPopulatedZone(radius, maxTries) + return p, dx, dz +end + +--[[-- +function dmlZone:createRandomPointInPopulatedZone(radius, maxTries) + if not maxTries then maxTries = 20 end + local cnt = 0 + local p, dx, dz + p, dx, dz = self:createRandomPointInZone() -- p is x, 0, z + repeat + local hits = cfxZones.objectsInRange(p, radius) + if hits < 1 then return p, dx, dz end + -- move to the right by radius + p.z = p.z + radius + dz = dz + radius + cnt = cnt + 1 + trigger.action.outText("failed try " .. cnt, 30) + until cnt > maxTries + return p, dx, dz +end +--]]-- +function cfxZones.objectHandler(theObject, theCollector) -- for world.search + table.insert(theCollector, theObject) + return true +end + +function cfxZones.objectsInRange(pt, range) + if not range then range = 100 end -- meters + local allCats = {1, 2, 3, 4, 5, 6} -- all cats + local lp = {x = pt.x, y = pt.z} + pt.y = land.getHeight(lp) + local collector = {} + -- now build the search argument + local args = { + id = world.VolumeType.SPHERE, + params = { + point = pt, + radius = range -- range + } + } + -- now call search + world.searchObjects(allCats, args, cfxZones.objectHandler, collector) + -- now filter for distance because search finds too many + local filtered = {} + for idx, anObject in pairs(collector) do + -- calc dist and filter + local op = anObject:getPoint() + local dist = dcsCommon.dist(pt, op) + if dist < range then +-- local e = { +-- dist = dist, +-- o = anObject +-- } +-- table.insert(filtered, e) + table.insert(filtered, anObject) + end + end + + return #filtered, filtered +end + function cfxZones.addZoneToManagedZones(theZone) local upperName = string.upper(theZone.name) -- newZone.name:upper() cfxZones.zones[upperName] = theZone diff --git a/modules/civHelo.lua b/modules/civHelo.lua new file mode 100644 index 0000000..ff02a94 --- /dev/null +++ b/modules/civHelo.lua @@ -0,0 +1,474 @@ +civHelo = {} +civHelo.version = "1.0.0" +civHelo.requiredLibs = { + "dcsCommon", -- always + "cfxZones", +} +--[[-- +Version History + 1.0.0 - Initial version + +--]]-- + +civHelo.flights = {} -- currently active flights +civHelo.ports = {} -- civHelo zones where flight can take off and land +civHelo.maxDist = 600000 -- 60 km +civHelo.minDist = 1000 -- 1 km +-- helos and liveries +civHelo.types = {"CH-47D", "CH-53E", "Ka-27", "Mi-24V", "Mi-26", "Mi-28N", "Mi-8MT","OH-58D", "SA342L", "SH-60B", "UH-1H", "UH-60A",} -- default set + +civHelo.liveries = { +["CH-47D"] = {"Australia RAAF", "ch-47_green neth", "ch-47_green spain", "ch-47_green uk", "Greek Army", "standard", }, +["CH-53E"] = {"standard",}, +["Ka-27"] = {"China PLANAF", "standard", "ukraine camo 1",}, +["Mi-24V"] = {"Abkhazia", "Algerian AF Black", "Algerian AF New Desert", "Algerian AF Old Desert", "Russia_FSB", "Russia_MVD", "South Ossetia", "standard", "standard 1", "standard 2 (faded and sun-bleached)", "ukraine", "Ukraine UN", }, +["Mi-26"] = {"7th Separate Brigade of AA (Kalinov)", "Algerian Air Force SL-22", "China Flying Dragon Aviation", "RF Air Force", "Russia_FSB", "Russia_MVD", "United Nations", }, +["Mi-28N"] = {"AAF SC-11", "AAF SC-12", "night", "standard", }, +["Mi-8MT"] = {"China UN", "IR Iranian Special Police Forces", "Russia_Gazprom", "Russia_PF_Ambulance", "Russia_Police", "Russia_UN", "Russia_UTair", "Russia_Aeroflot", "Russia_KazanVZ", "Russia_LII_Gromov RA-25546", "Russia_Vertolety_Russia", "Russia_Vertolety_Russia_2", "Russia_Naryan-Mar", }, +--["OH-58D"] = {"",}, +--["SA342L"] = {"",}, +["SH-60B"] = {"Hellenic Navy", "standard", }, +["UH-1H"] = {"[Civilian] Medical", "[Civilian] NASA", "[Civilian] Standard", "[Civilian] VIP", "Greek Army Aviation Medic", "Italy 15B Stormo S.A.R -Soccorso", "Norwegian Coast Guard (235)", "Norwegian UN", "Spanish UN", "USA UN", }, +["UH-60A"] = {"ISRAIL_UN", } +} +-- +-- process civHelo zone +-- +function civHelo.readCivHeloZone(theZone) + -- process properties + theZone.canLand = theZone:getBoolFromZoneProperty("land", true) + theZone.canStart = theZone:getBoolFromZoneProperty("start", true) + theZone.hotStart = theZone:getBoolFromZoneProperty("hot", true) + + if theZone:hasProperty("types") then + local hTypes = theZone:getStringFromZoneProperty("types", "xxx") + local typeArray = dcsCommon.splitString(hTypes, ",") + typeArray = dcsCommon.trimArray(typeArray) + theZone.types = typeArray + end + -- set active flag + theZone.inUse = nil -- if true zone is in use for a flight +end + +function civHelo.addCivHeloZone(theZone) + table.insert(civHelo.ports, theZone) +end + +function civHelo.getPortNamed(name) + for idx, theZone in pairs(civHelo.ports) do + if theZone.name == name then return theZone end + end + if civHelo.verbose then + trigger.action.outText("+++civH: cannot find port <" .. name .. ">", 30) + end + return nil +end + +function civHelo.getFreePort(source, dest, anchor) + collector = {} + local a + if anchor then a = anchor:getPoint() end -- for dist calc + for idx, theZone in pairs(civHelo.ports) do + if theZone.inUse then + else + if (source and theZone.canStart) or + (dest and theZone.canLand) then + if anchor then + -- must be at least minDist and at most maxDist away + local p = theZone:getPoint() + local d = dcsCommon.dist(a, p) + if d > civHelo.minDist and d < civHelo.maxDist then + table.insert(collector, theZone) + else +-- trigger.action.outText("+++civH: disregarded dest zone <" .. theZone.name .. ">: dist <" .. math.floor(d) / 1000 .. " km> out of bounds", 30) + end + else + table.insert(collector, theZone) + end + end + end + end + if #collector < 1 then return nil end + local theZone = dcsCommon.pickRandom(collector) + return theZone +end + +function civHelo.getSourceAndDest() + local source = civHelo.getFreePort(true, false) + if not source then return nil, nil end + source.inUse = true + local dest = civHelo.getFreePort(false, true, source) + if not dest then + source.inUse = nil + return nil, nil + end + dest.inUse = true + return source, dest +end + + +function civHelo.createCommandTask(theCommand, num) + if not num then num = 1 end + local t = {} + t.enabled = true + t.auto = false + t.id = "WrappedAction" + t.number = num + local params = {} + t.params = params + local action = {} + params.action = action + action.id = "Script" + local p2 = {} + action.params = p2 + p2.command = theCommand + return t +end + +function civHelo.createLandTask(p, duration, num) + if not num then num = 1 end + local t = {} + t.enabled = true + t.auto = false + t.id = "ControlledTask" + t.number = num + local params = {} + t.params = params + + local ptsk = {} + params.task = ptsk + ptsk.id = "Land" + local ptp = {} + ptsk.params = ptp + ptp.x = p.x + ptp.y = p.z + ptp.duration = "300" -- not sure why + ptp.durationFlag = false -- off anyway + local stopCon = {} + stopCon.duration = duration + params.stopCondition = stopCon + + return t +end + +function civHelo.createFlight(name, theType, fromZone, toZone) --, inAir) + if not fromZone then return nil end + if not toZone then return nil end +-- if not inAir then inAir = false end + + local theGroup = dcsCommon.createEmptyAircraftGroupData (name) + local theHUnit = dcsCommon.createAircraftUnitData(name .. "-H", theType, false) + if fromZone.hdg then + + end + -- add livery capability for this aircraft + --civHelo.processLiveriesFor(theHUnit, theType) + civHelo.getLiveryForType(theType, theHUnit) + + -- enforce civ attribute + theHUnit.civil_plane = true + + theHUnit.payload.fuel = 5000 -- 5t of fuel + dcsCommon.addUnitToGroupData(theHUnit, theGroup) + + local A = fromZone:getPoint() + local B = toZone:getPoint() + + -- unit is done, let's do the route + -- WP 1: take off + local fromWP, omwWP + fromWP = dcsCommon.createTakeOffFromGroundRoutePointData(fromZone:getPoint(true), fromZone.hotStart) -- last true = hot + fromWP.alt_type = "RADIO" -- AGL instead of MSL + theHUnit.alt = fromWP.alt + -- WP2: signal that we are 1km away so source can be freed from flight + local dir = dcsCommon.bearingFromAtoB(A, B) -- x0z coords + local omw = dcsCommon.pointInDirectionOfPointXYY(dir, 1000, A) + omwWP = dcsCommon.createSimpleRoutePointData(omw, civHelo.alt, civHelo.speed) + omwWP.alt_type = "RADIO" + -- create a command waypoint + local task = {} + task.id = "ComboTask" + task.params = {} + local ttsk = {} + local command = "civHelo.departedCB('" .. name .. "', '" .. fromZone:getName() .. "')" + ttsk[1] = civHelo.createCommandTask(command,1) + task.params.tasks = ttsk + omwWP.task = task + + -- now set up destination point: land + -- at destination and add a small script + local toWP + -- add destination WP. this is common to both + toWP = dcsCommon.createSimpleRoutePointData(toZone:getPoint(), civHelo.alt, civHelo.speed) + toWP.alt_type = "RADIO" + + local task = {} + task.id = "ComboTask" + task.params = {} + local ttsk = {} + local p = toZone:getPoint() + ttsk[1] = civHelo.createLandTask(p, civHelo.landingDuration, 1) + local command = "civHelo.landedCB('" .. name .. "', '" .. toZone:getName() .. "')" + ttsk[2] = civHelo.createCommandTask(command,2) + task.params.tasks = ttsk + toWP.task = task + + -- move group to WP1 and add WP1 and WP2 to route + dcsCommon.moveGroupDataTo(theGroup, + fromWP.x, + fromWP.y) + dcsCommon.addRoutePointForGroupData(theGroup, fromWP) + if not inAir then + dcsCommon.addRoutePointForGroupData(theGroup, omwWP) + end + dcsCommon.addRoutePointForGroupData(theGroup, toWP) + + -- spawn + local groupCat = Group.Category.HELICOPTER + local theSpawnedGroup = coalition.addGroup(civHelo.owner, groupCat, theGroup) + + return theSpawnedGroup +end + +function civHelo.openPort(where) + local thePort = civHelo.getPortNamed(where) + if thePort then + thePort.inUse = nil + end + if civHelo.verbose then trigger.action.outText("+++civH: opening port <" .. where .. ">", 30) end +end + +function civHelo.openPortUsedBy(name) + for idx, theZone in pairs(civHelo.ports) do + if theZone.inUse == name then + theZone.inUse = nil + if civHelo.verbose then + trigger.action.outText("+++civH: clearing port <" .. theZone.name .. "> from flight <" .. name .. ">", 30) + end + end + end +end + +function civHelo.departedCB(who, where) + -- free the port that we just took off from + civHelo.openPort(where) +end + +function civHelo.landedCB(who, where) + -- step 1: remove the flight + local theGroup = Group.getByName(who) + if theGroup then + if Group.isExist(theGroup) then + Group.destroy(theGroup) + end + else + trigger.action.outText("+++civH: cannot find group <" .. who .. ">", 30) + end + civHelo.flights[who] = nil + + -- step 2: schedule opening the port + -- do it immediately first + civHelo.openPort(where) +end + +-- +-- new flight +-- +function civHelo.getType(theZone) + local types = civHelo.types -- load default + if theZone.types then types = theZone.types end + local hType = dcsCommon.pickRandom(types) + return hType +end + +function civHelo.getLiveryForType(theType, theData) + if civHelo.liveries[theType] then + local available = civHelo.liveries[theType] + local chosen = dcsCommon.pickRandom(available) + theData.livery_id = chosen + end +end + +function civHelo.newFlight() + local source, dest = civHelo.getSourceAndDest() + if source and dest then + -- source and dest "inUse" already have been marked inUse + -- but still need the name of the flight + local theType = civHelo.getType(source) + local name = source:getName() .. "-" .. dest:getName() + local theFlight = civHelo.createFlight(name, theType, source, dest) + if theFlight then + civHelo.flights[name] = theFlight + source.inUse = name + dest.inUse = name + if civHelo.verbose then + trigger.action.outText("+++civH: created new flight <" .. name .. ">", 30) + end + else + trigger.action.outText("+++civH: cant create flight <" .. name .. ">", 30) + source.inUse = nil + dest.inUse = nil + end + else + if civHelo.verbose then + trigger.action.outText("+++civH: no ports available, can't create new flight. Numflights = <" .. dcsCommon.getSizeOfTable(civHelo.flights) .. ">", 30) + end + end +end + +-- +-- event handler +-- +function civHelo:onEvent(theEvent) +-- trigger.action.outText("event", 30) + if not theEvent.initiator then return end + local theUnit = theEvent.initiator + if not theUnit.getGroup then return end + local theGroup = theUnit:getGroup() + if not theGroup then return end + local gName = theGroup:getName() + + -- see if it's an event for one of mine + local mine = false + for name, aGroup in pairs(civHelo.flights) do + if name == gName then mine = true end + end + if not mine then + return + end + + local id = theEvent.id + if id == 9 or -- pilot dead + id == 30 or -- unit lost + id == 5 -- crash + then + if civHelo.verbose then + trigger.action.outText("+++civH: cancelling flight <" .. gName .. ">: mishap", 30) + end + civHelo.openPortUsedBy(gName) + civHelo.flights[gName] = nil + end +end + +-- +-- update +-- +function civHelo.update() + -- schedule again + timer.scheduleFunction(civHelo.update, {}, timer.getTime() + 1/civHelo.ups ) + + -- see how many flights are live + if dcsCommon.getSizeOfTable(civHelo.flights) < civHelo.maxFlights then + civHelo.newFlight() + end +end + +-- +-- Config +-- +function civHelo.addTypesAndLiveries(rawIn) + local newTypes = {} + local newLiveries = {} + -- now iterate the input table, and generate new types and + -- liveries from it + for theType, liveries in pairs (rawIn) do + if civHelo.verbose then + trigger.action.outText("+++civH: processing type <" .. theType .. ">:<" .. liveries .. ">", 30) + end + local livA = dcsCommon.splitString(liveries, ',') + livA = dcsCommon.trimArray(livA) + table.insert(newTypes, theType) + newLiveries[theType] = livA + end + + return newTypes, newLiveries +end + +function civHelo.readConfigZone() + local theZone = cfxZones.getZoneByName("civHeloConfig") + if not theZone then + theZone = cfxZones.createSimpleZone("civHeloConfig") + end + civHelo.verbose = theZone.verbose + civHelo.owner = theZone:getNumberFromZoneProperty("country", 82) --82 -- UN peacekeepers + civHelo.ups = theZone:getNumberFromZoneProperty("ups", 1 / 30) + civHelo.maxFlights = theZone:getNumberFromZoneProperty("maxFlights", 5) + civHelo.landingDuration = theZone:getNumberFromZoneProperty("landingDuration", 180) -- seconds = 3 minutes + civHelo.alt = theZone:getNumberFromZoneProperty("alt", 100) -- 100 m + civHelo.speed = theZone:getNumberFromZoneProperty("speed", 30) + civHelo.maxDist = theZone:getNumberFromZoneProperty("maxDist", 60000) + civHelo.minDist = theZone:getNumberFromZoneProperty("minDist", 1000) + if theZone:hasProperty("types") then + local hTypes = theZone:getStringFromZoneProperty("types", "xxx") + local typeArray = dcsCommon.splitString(hTypes, ",") + typeArray = dcsCommon.trimArray(typeArray) + civHelo.types = typeArray + end + + -- now get types and liveries from 'helo_liveries' if present + local livZone = cfxZones.getZoneByName("helo_liveries") + if livZone then + if civHelo.verbose then + trigger.action.outText("civH: found and processing 'helo_liveries' zone data.", 30) + end + + -- read all into my types registry, replacing whatever is there + local rawLiver = cfxZones.getAllZoneProperties(livZone) + local newTypes, newLiveries = civAir.addTypesAndLiveries(rawLiver) + -- now types to existing types if not already there + for idx, aType in pairs(newTypes) do + dcsCommon.addToTableIfNew(civHelo.types, aType) + if civHelo.verbose then + trigger.action.outText("+++civH: processed and added helo <" .. aType .. "> to civHelo", 30) + end + end + -- now replace liveries or add if not already there + for aType, liveries in pairs(newLiveries) do + civHelo.liveries[aType] = liveries + if civHelo.verbose then + trigger.action.outText("+++civH: replaced/added liveries for helicopter <" .. aType .. ">", 30) + end + end + end +end + +-- +-- start +-- +function civHelo.start() +-- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx civ helo requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx civ helo", civHelo.requiredLibs) then + return false + end + + -- read config + civHelo.readConfigZone() + + -- process civHelo Zones + -- old style + local attrZones = cfxZones.getZonesWithAttributeNamed("civHelo") + for k, aZone in pairs(attrZones) do + civHelo.readCivHeloZone(aZone) -- process attributes + civHelo.addCivHeloZone(aZone) -- add to list + end + + -- start update in 5 seconds + timer.scheduleFunction(civHelo.update, {}, timer.getTime() + 5) + + -- install event handler + world.addEventHandler(civHelo) + + -- say hi + trigger.action.outText("civHelo v" .. civHelo.version .. " started.", 30) + return true +end + +if not civHelo.start() then + trigger.action.outText("civHelo failed to start.") + civHelo = nil +end + diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index 25b7376..8529da8 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -303,7 +303,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" if theZone:hasProperty("wholeGroups") then theZone.centerOnly = theZone:getBoolFromZoneProperty( "wholeGroups", false) end - + if theZone:hasProperty("inBuiltup") then + theZone.inBuiltup = theZone:getNumberFromZoneProperty("inBuiltup", 10) -- 10 meter radius must be free -- small houses + end theZone.rndHeading = theZone:getBoolFromZoneProperty("rndHeading", false) theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false) @@ -1063,6 +1065,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) local loc, dx, dy if spawnZone.onPerimeter then loc, dx, dy = spawnZone:createRandomPointOnZoneBoundary() + elseif spawnZone.inBuiltup then + loc, dx, dy = spawnZone:createRandomPointInPopulatedZone(spawnZone.inBuiltup) else loc, dx, dy = spawnZone:createRandomPointInZone() -- also supports polygonal zones end @@ -1072,6 +1076,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- *every unit's displacement is randomized if spawnZone.onPerimeter then loc, dx, dy = spawnZone:createRandomPointOnZoneBoundary() + elseif spawnZone.inBuiltup then + loc, dx, dy = spawnZone:createRandomPointInPopulatedZone(spawnZone.inBuiltup) else loc, dx, dy = spawnZone:createRandomPointInZone() end @@ -1322,6 +1328,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) local loc, dx, dy if spawnZone.onPerimeter then loc, dx, dy = spawnZone:createRandomPointOnZoneBoundary() + elseif spawnZone.inBuiltup then + loc, dx, dy = spawnZone:createRandomPointInPopulatedZone(spawnZone.inBuiltup) else loc, dx, dy = spawnZone:createRandomPointInZone() -- also supports polygonal zones end diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index 1fa631a..52b5d8f 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -1,5 +1,5 @@ csarManager = {} -csarManager.version = "3.1.0" +csarManager.version = "3.2.0" csarManager.ups = 1 --[[-- VERSION HISTORY @@ -28,9 +28,14 @@ csarManager.ups = 1 - inflight status reflects time limit but will not time out - pickupSound option - lostSound option +- 3.1.1 - birth clears troopsOnBoard +- 3.2.0 - inPopulated csar option + - clearance csar attribute + - maxTries csar attribute - INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED - INTEGRATES WITH LIMITED AIRFRAMES IF INSTALLED + INTEGRATES AUTOMATICALLY WITH playerScore + INTEGRATES WITH LIMITED AIRFRAMES + INTEGRATES AUTOMATICALLY WITH SCRIBE --]]-- -- modules that need to be loaded BEFORE I run @@ -297,17 +302,26 @@ function csarManager:onEvent(event) csarManager.heloLanded(theUnit) end - if ID == 3 then -- take off + if ID == 3 or ID == 55 then -- take off, postponed take-off csarManager.heloDeparted(theUnit) end if ID == 5 then -- crash csarManager.heloCrashed(theUnit) + -- note: maybe not called in network missions. + -- correction: is called in 2.9 end if ID == 15 then -- player helicopter birth -- we need to set up comms for this unit csarManager.setCommsMenu(theUnit) + -- we also need to make sure that there are no + -- more troopsOnBoard + + local conf = csarManager.getUnitConfig(theUnit) + conf.unit = theUnit + conf.troopsOnBoard = {} + end end @@ -421,7 +435,7 @@ function csarManager.heloLanded(theUnit) end for idx, msn in pairs(conf.troopsOnBoard) do - -- each troopsOnboard is actually the + -- each troopsOnBoard is actually the -- csar mission that I picked up csarManager.successMission(myName, base.name, msn) end @@ -792,7 +806,7 @@ function csarManager.doStatusCarrying(args) local evacMission = conf.troopsOnBoard[i] report = report .. "\n".. i .. ") " .. evacMission.name if evacMission.expires then - delta = math.floor ((mission.expires - now) / 60) + delta = math.floor ((evacMission.expires - now) / 60) if delta > 20 then report = report .. " is hurt but stable" elseif delta > 10 then @@ -1207,6 +1221,9 @@ function csarManager.createCSARMissionFromZone(theZone) if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end if theZone.onRoad then mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z) + elseif theZone.inPopulated then + local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance, theZone.maxTries) + mPoint = aPoint -- safety in case we need to mod aPoint end local theMission = csarManager.createCSARMissionData( mPoint, @@ -1218,6 +1235,7 @@ function csarManager.createCSARMissionFromZone(theZone) theZone.csarMapMarker, -- mapMarker 0.1, --theZone.radius) -- radius nil) -- parashoo unit + theMission.inPopulated = theZone.inPopulated -- transfer for csarFX return theMission end @@ -1338,12 +1356,22 @@ function csarManager.readCSARZone(theZone) theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true) theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false) + theZone.inPopulated = theZone:getBoolFromZoneProperty("inPopulated", false) + theZone.clearance = theZone:getNumberFromZoneProperty("clearance", 10) + theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20) + + if theZone.onRoad and theZone.inPopulated then + trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30) + end if (not deferred) then 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) + elseif theZone.inPopulated then + local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance, theZone.maxTries) + mPoint = aPoint -- safety in case we need to mod aPoint end local theMission = csarManager.createCSARMissionData( mPoint, diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index d3dcfc4..ce3077a 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "3.0.2" +dcsCommon.version = "3.0.3" --[[-- VERSION HISTORY 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false - point2text new intsOnly option @@ -11,6 +11,8 @@ dcsCommon.version = "3.0.2" 3.0.1 - clone: better handling of string type 3.0.2 - new getPlayerUnit() 3.0.3 - createStaticObjectForCoalitionInRandomRing() returns x and z + - isTroopCarrier() also supports 'helo' keyword + - new createTakeOffFromGroundRoutePointData() --]]-- -- dcsCommon is a library of common lua functions @@ -1285,6 +1287,25 @@ dcsCommon.version = "3.0.2" return route end + function dcsCommon.createTakeOffFromGroundRoutePointData(pt, isHot) -- vec 3! + if not pt then return nil end + + local rp = {} + rp.x = pt.x + rp.y = pt.z + rp.alt = pt.y + if isHot then + rp.action = "From Ground Area Hot" + rp.type = "TakeOffGroundHot" + else + rp.action = "From Ground Area" -- add " Hot" if hot + rp.type = "TakeOffGround" -- add "Hot" (NO blank) if hot + end + rp.speed = 10 -- that's 36 km/h + rp.alt_type = "BARO" + return rp + end + function dcsCommon.createTakeOffFromParkingRoutePointData(aerodrome) if not aerodrome then return nil end @@ -2700,7 +2721,16 @@ end function dcsCommon.isTroopCarrier(theUnit, carriers) -- return true if conf can carry troups - if not theUnit then return false end + if not theUnit then return false end + + -- see if carriers contains "helo" and theUnit is a helo + if dcsCommon.arrayContainsString(carriers, "helo") or dcsCommon.arrayContainsString(carriers, "helos")then + local grp = theUnit:getGroup() + if grp:getCategory() == 1 then -- NOT category bug prone, is a group check + return true + end + end + local uType = theUnit:getTypeName() return dcsCommon.isTroopCarrierType(uType, carriers) end diff --git a/modules/scribe.lua b/modules/scribe.lua index 125ad2a..f5b8888 100644 --- a/modules/scribe.lua +++ b/modules/scribe.lua @@ -1,5 +1,5 @@ scribe = {} -scribe.version = "1.0.0" +scribe.version = "1.0.1" scribe.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -9,7 +9,8 @@ scribe.requiredLibs = { --[[-- Player statistics package VERSION HISTORY - 1.0.0 Initial Version + 1.0.0 Initial Version + 1.0.1 postponed land, postponed takeoff, unit_lost --]]-- scribe.verbose = true scribe.db = {} -- indexed by player name @@ -234,8 +235,9 @@ function scribe.playerEjected(playerName) end function scribe.playerDied(playerName) --- trigger.action.outText("+++scb: player <" .. playerName .. "> DEAD event, handing off to crashS", 30) - -- counts as a crash + if scribe.verbose then + trigger.action.outText("+++scb: player <" .. playerName .. "> DEAD event, handing off to crashS", 30) + end -- counts as a crash local theEntry = scribe.getPlayerNamed(playerName) if not theEntry.isActive then if scribe.verbose then @@ -365,7 +367,9 @@ function scribe:onEvent(theEvent) scribe.playerUnits[uName] = playerName -- for crash helo detection end - if theEvent.id == 8 or theEvent.id == 9 then -- dead, pilot_dead + if theEvent.id == 8 or + theEvent.id == 9 or + theEvent.id == 30 then -- dead, pilot_dead, unit_lost scribe.playerDied(playerName) end @@ -373,15 +377,17 @@ function scribe:onEvent(theEvent) scribe.playerEjected(playerName) end - if theEvent.id == 5 then -- crash + if theEvent.id == 5 then -- crash, maybe not called in MP scribe.playerCrashed(playerName) end - if theEvent.id == 4 then -- landed + if theEvent.id == 4 or -- landed + theEvent.id == 56 then scribe.playerLanded(playerName) end - if theEvent.id == 3 then -- take-off + if theEvent.id == 3 or -- take-off + theEvent.id == 55 then -- postponed take-off scribe.playerDeparted(playerName) -- trigger.action.outText("departure detected", 30) end diff --git a/modules/valet.lua b/modules/valet.lua index 11e2048..4099ab3 100644 --- a/modules/valet.lua +++ b/modules/valet.lua @@ -1,5 +1,5 @@ valet = {} -valet.version = "1.0.2" +valet.version = "1.0.3" valet.verbose = false valet.requiredLibs = { "dcsCommon", -- always @@ -12,6 +12,7 @@ valet.valets = {} 1.0.0 - initial version 1.0.1 - typos in verbosity corrected 1.0.2 - also scan birth events + 1.0.3 - outSoundFile now working correctly --]]-- @@ -41,7 +42,7 @@ function valet.createValetWithZone(theZone) theZone.firstInSoundFile = cfxZones.getStringFromZoneProperty(theZone, "firstInSoundFile", "") end - theZone.outSoundFile = cfxZones.getStringFromZoneProperty(theZone, "outSoundFile", "") + theZone.outSoundFile = cfxZones.getStringFromZoneProperty(theZone, "outSoundFile", theZone.inSoundFile) -- greeting/first greeting, handle if "" = no text out if cfxZones.hasProperty(theZone, "firstGreeting") then @@ -195,7 +196,7 @@ function valet.sendOffPlayer(playerName, aPlayerUnit, theZone, theDesc) -- player has left the area local msg = theZone.goodbye or "" local dur = theZone.duration - local fileName = "l10n/DEFAULT/" .. theZone.inSoundFile + local fileName = "l10n/DEFAULT/" .. theZone.outSoundFile local ID = aPlayerUnit:getID() if msg == "" then msg = "" end diff --git a/tutorial & demo missions/demo - On the Record.miz b/tutorial & demo missions/demo - On the Record.miz index 223b30e..e5ee15c 100644 Binary files a/tutorial & demo missions/demo - On the Record.miz and b/tutorial & demo missions/demo - On the Record.miz differ diff --git a/tutorial & demo missions/demo - civvy heli.miz b/tutorial & demo missions/demo - civvy heli.miz new file mode 100644 index 0000000..c0b782a Binary files /dev/null and b/tutorial & demo missions/demo - civvy heli.miz differ