Jump to content


  • Posts

  • Joined

  • Last visited

Everything posted by cfrag

  1. This reminds me of a line in the classic US SitCom "Friends" when main protagonist Ross' mother said something about another person that Ross was jealous of: "well, he's a real doctor", which greatly upset Ross. The joke being that Ross, having a PhD in Paleontology is a real doctor, while the other guy "merely" was an MD (some inside academia regard an MD scientifically to be an inferior form of doctorate compared to a fully-blown PhD in other disciplines). Not all doctors are medical doctors, which can cause confusion outside of academia (and is a frequent punch line in "Friends"). Even more so with pilots. My grandfather was a pilot. His job was to make sure large ships could safely leave the harbour. Some pilots steer ships and of those, most can't fly aircraft. Unlike a rose, a pilot isn't a pilot is a pilot is a pilot. Anyway - I feel the underlying question here is: will the F15-E be fun to play in single-player (ok, no doubt about that), and can the player while flying the aircraft employ the full range of weapons by themselves without having to hot-switch seats nor commanding an AI? Unlike, for example, the Apache, which "only" allows you to indirectly operate some weapons via AI (I love the Apache, and understand why this segregation of duties is implemented. For me it makes it slightly less satisfying to play compared to, say, the Hog, Shark, or Bug where I can do that directly). So, in Razbam's module for DCS, can the F15E's PIC (single-player) fly the plane and deliver all weaponry by themselves (like e.g., in a Hornet) without switching seats or using AI? How about when on an MP server (it bugs me to no end that I can't hot-swap in the Apache when on an MP server)? I guess I'll purchase anyway, but I'd still like to know
  2. I believe that may be what the OP is complaining about: flat-screen users abusing a feature that is intended to help VR players take a look at their controls. VR players have no 'normal' zoom function like non-VR players have, and (due to the fact that even top shelf goggles still have too low a resolution to clearly read small gauges or MFD symbology from 3 feet or more away) without the VR zoom would have to move their head closer to the dashboard, losing sight of their objective. It helps very little with identifying outside features (it merely moves the world's projection plane closer to the camera, a very disconcerting, not to say cheap effect), and the parallax shift alone severely limits this feature's usability (a correct zoom would reduce the FoV like a zoom lens, increasing object resolution. VR Zoom doesn't work that way, it just shoves the camera forward by a few feet). I have no idea if flat-screeners can use VR Zoom on top of their normal zoom ability and thus are able to abuse this to get an artificial 'eagle eye'. If they do, that would indeed be a possible cheat. Believing that VR users have an unfair advantage over flat-screeners with VR Zoom is doubtful as their normal resolution sucks to begin with (they do have an enormous advantage in spatial tracking and orientation, but definitely not in far sight nor outside resolution). VR giveth when it come to experience and taketh in other places like visual clarity. I play almost exclusively VR (once you played VR there is no way back), yet I'm still being outperformed by flatters. That's because they are better pilots than I am. VR doesn't make you better, it just makes you feel better when flying In any way, I don't think it wise to try and micro-manage a server's feature set for a certain set of users. If you believe someone is cheating, tell them and come to a gentleperson's agreement, or ban them from your server. Life's too short to play with jerks; don't waste your precious time to try and come up with measures that they will circumvent anyhow, yet place burdens on everyone else. Ban them, forget them; be too busy to have fun. Anyway, that's my server policy. I'm renting that server to have fun with fun people, not to argue with immature F--s. -ch
  3. Version: 0.8.3 -- 20220701 What? A tool for mission designers to debug flags Debugging. Ugh. The reason many of us don't write missions. Because just putting stuff together in a way that seems sensible simply doesn't work in DCS. Especially when using flags. When you deal with flags, which are as essential to missions as they are under-documented (which is to say "critically"), you are mostly up the creek without a paddle, and sinking. Rapidly. So here it is. Yes, late - but I can take a hint -- in full DML-enhanced glory and in-editor interactivity: a flag debugger. For any mission, even without DML (this version is stand-alone). With typical easy-to-use ME-based interface. With custom 'break points', on-the-fly enable/disable, and fully customizable debug messages - for any flag that you want to debug. As you can imagine (since it's based on DML), all you need to to is place a trigger zone (anywhere you want), and add the 'debug?' attribute with a list of the flags that you want to debug. Any time that one of the listed flags changes its value, you'll see a message like this: <time>: <flag name> changed from <value> to <value>: That's the most basic way the debugger works. So if you want to debug (i.e. observe) flag "enemyDetected7337", simply create a zone, add the 'debug?' attribute with a value of "enemyDetected7337" Of course - being DML - there's a lot more flexibility if you need it. You can tell the debugger (on a flag by flag basis) to only speak up when certain conditions are met by using the 'sayWhen' attribute and any of the published DML Watchflag conditions A zone configured like the one above will only output a debug message when the value of flag "t1" changes to 4 ("=4") Oh, and the debug message is also freely customizable. By default the debugger says <time>: <flag name> changed from <value> to <value> Of course, you can change that per zone to anything you want. And you have access to standard DML 'wildcards' such as time <t>, zone name <z>, flag name <f>, original value <o>, current value <c> etc. which will result in something like this: Check out the demo and start experimenting Why now? I'm nearing completion of the debugger, and I would like to solicit your feedback here before I include it in DML. The demo mission is bare-bones to show that it's working and give you a head start in setting it up. To use the debugger in your missions, open it in ME, copy/paste the contents of the attached text file "DML Debugger" into a DOSCRIPT action, and then start adding trigger zones and attributes for the flags that you want to debug. Run the mission, and wait for the debug messages to appear. NOTE: If you are using DML in your mission, make sure to add this script last, as it replaces existing versions of dcsCommon and cfxFlags with newer ones. The debugger supports full DML input and output trigger handling, so you know what to do -- Full documentation to follow. SEPARATE FROM DML? Yes and no. I'm planning to update this stand-alone version of Flag Debugger from time to time for people who hate having fun and only want some debugging abilities. If you use DML, you'll always have the newest and greatest version as part of its normal distribution. Have fun, -ch DML Debugger.zip demo - Bug Hunt.miz
  4. That's probably because these holes are part of the module's geometry (canopy) and textures (holes/transparency/diffraction). That Huey broken glass is indeed something to behold (especially in VR), and made my jaw drop to the floor the first time I encountered it (didn't survive, but man that was cool). It's also a legitimate impediment for precision flight/landings, so it adds realism. I too wish this to be something that is expanded upon. That being said (helicopters aside), are bullet holes in a jet fighter's canopy (not warbirds, jet fighter) something that is survivable? There seems awfully little leeway for a bullet to penetrate the canopy and not hit something critical. Being hit by a 20mm round or worse sounds quite terminal to me... Now, I still want such an effect, but I wonder...
  5. Version 1.02 - 20220630 IMPOSTORS! or: switch groups between being AI-controlled and static objects at will. All AI units consume CPU - even if they have to wait until it's their time to become active. "Impostors" is an easy way to turn AI units into static objects while they wait, and then, when they should become active, turn them back into AI-controlled units (and yes, you can hot-spawn aircraft at parking, so they a ready to roll the second you activate them). Or, when they reach their destination and are supposed to become inactive eye candy, turn any AI group into a collection of static objects and take the load off your CPU. For example, you can place a group of ground units on the map, turn them into static objects, and only activate them if the player comes close enough - an ideal trap, so to speak (imagine an S-300 triggered to AI only when you are already inside the lethal range) So why call them 'imposters'? In many 3D game engines, developers use a technique called 'imposters' to conserve CPU. Impostors are simplified stand-ins (often nothing more than painted billboards) for more detailed game objects that are too far away for the player to notice the difference. Since unit AI is a drag on CPU, static objects can function as AI impostors and help reduce CPU demand when it is not required for these units to be controlled. Changes Documentation - Impostor module - Impossible Impostors demo Demos - Impossible Impostors Modules - impostors 1.0.0 - cfxZones 2.8.1 - groupTracker 1.1.3 Enjoy, -ch
  6. Single unit support still isn't fully implemented in the current mission scripting environment, but we are getting closer. We've recently seen the (really welcome) introduction of an outTextForUnit() method that allows scripts to send messages to individual units, but until there's a method to install commands to individual units (not just groups), single unit groups are the only way to go when your mission requires single-unit command abilities (like ordering troops aboard your helicopter, or disembarking troops from your helicopter). So, by all means, use multi-client groups, but avoid it when you need to be able to issue unit-specific commands from the "F10 Other..." communication tree. -ch
  7. Version 1.01 - 20220623 Featured Updates: Recon Mode / Guardian Angel Now with 1.0 under the belt, I finally found the time to polish two modules that are great, but barely on a level with other DML modules: Recon Mode and Guardian Angel. Recon mode had a clunky interface and positively squandered the potential to trigger events when an aircraft detects a group. And Guardian Angel did not handle missiles well that are controlled by advanced SAM sites who can re-target their missiles in the air. All that is in the past now. First, Recon Mode gained more features than you can shake a pair of binoculars at: MUCH better reporting inclding Lat/Lon or MGRS of discovered group Selective inclusion / exclusion of aircraft with trigger zones Trigger-Zone based Priority- and Blacklisting Trigger-Zone based flags when groups are discovered (only priority targets) Detections can now trigger game events Custom priority messages per priority zone Support for cloned groups Ability to individually suppress reports and map marks so detection events can be triggered without generating a report Turn Recon Mode on and off via flags And Guardian Angel has gained the following abilities: Correctly handles re-targeting missiles Dynamically turn Guardian Angel on and off during the mission Adding Aircraft to an ‘unprotected’ list using trigger zones Adding Aircraft to a ‘protected’ list using trigger zones And of course, they each have their own demo with full discussion of the new features Details Documentation - Added Guardian Angel Reloaded Documentation - Added Recon Mode Reloaded doc Demos - updated Guardian Angel Reloaded - Recon Mode Reloaded Modules - changer 1.0.1 - clone zone 1.4.8 - guardian angel 3.0.1 - recon mode 2.1.0 Enjoy, -ch
  8. OK, I see the issue. It's fixed in the included mission below. Root cause is a missing guard in the changer module. I added the new Changer version 1.0.1 so you don't have to wait until (tomorrows?) next DML drop. Other changes I've made (because I can't help myself ) Made the trigger that loads DML an 'ONSTART' trigger, set color to green, and renamed it to 'Load DML' Removed a duplicate copy of cfxPlayer as it was redundant Replaced changer 1.0.0 with (BRAND SPANKING NEW) 1.0.1 Cheers, -ch clonerTEST.miz
  9. I'm sure we can get to the root of that quickly. Why don't you post the mission here or PM it to me so I can have a look.
  10. This is a warning that you are using a flag name that DML deems suspicious. Please look at the zone named 'ZONE000' and check if you are using a flag named <none>. You can use that flag name if you really want, but DML will keep warning you that you are using a name that it deeply dislikes. This happens most commonly if you provide an empty value when you should have provided a flag name. So please also check if you have added an attribute for an input or output flag to ZONE000 that has an empty value.
  11. I'm not sure I correctly understand you question. What are you trying to achieve? When a changer starts up, by default it is set to direct, no evaluation, and it passes input to output unchanged (i.e. straight through). If you then send a signal into "changeOff?" the changer stops transmitting signals. Above shows an example cloner that is fully copy/pasteable and will clone any groups inside, and automatically respawn all groups inside when the previous clone was destroyed - UNTIL a signal is received on the (global) 'gameOver' flag (note that the trigger zone uses local flags to not pollute your flag space). This works by start a clone cycle at the mission start when all units are dead, (local) flag isDead is banged this runs into the changer's 'change?' input and is passed on to changeOut!, which bangs on the local (local) clone flag (local) clone is wired into the cloner's 'clone?' input and starts a new cycle. This continues until at one point you change the value of the (global) flag "gameOver" that runs into the changer's 'changeOff?' input. Once the change is detected, change? and changeOut! are no longer connected, and no further clones can spawn because the signal on (local) isDead can't pass the changer. Does this help?
  12. Currently not. Enclosed is a (98% finished) mission that randomly chooses convoys from an existing list of convoys and spawns them. The mission uses DML's 'cloners' to spawn groups from templates (which in itself can be randomized) and RND flag randomizers to choose one flag out of n each time they activate. DML's not as flexible as mist or moose, but should be easier to integrate with your missions when you need standard stuff like randomized group spawns while retaining Mission Editor control (DML uses trigger zones for all design stuff and requires no Lua knowledge). Island Defender.miz
  13. This is one of the main stumbling blocks for many scripts: when the dead event occurs, many scripts access the unit's group via getGroup(). Static objects have (currently) no getGroup() method implemented, so the invocation fails and your mission breaks when the unit is replaced by a static object and that static object subsequently invokes a dead event. There currently is no remedy if your code depends on retrieving the group (to track if a specific group has been destroyed) - you'll have to resort to regularly iterate all interesting groups and determine if they are still alive. If you are accessing the group just for completeness, something similar to local theGroup = nil if theUnit.getGroup then theGroup = theUnit:getGroup() end if theGroup then -- do whatever you want with the group else -- we probably have a static object end should help in your code.
  14. That's easily explained: I'm lazy - while each demo works by itself and to the extent that I want to show a feature, it's usually not using the most recent version of all modules (they are using the version from the time I created the demo - there currently are more than 50 demos in DML, and updating them for each release takes too much time for me). So when you add modules to an existing demo, there is a good chance that the module you add uses abilities that did not exist when the demo was created. Changer, for example, is a fairly recent module and requires very recent versions of dcsCommon and cfxZones. The remedy is a bit annoying, but simple: update at least dcsCommon and cfxZones (better all modules that are in the mission) to the newest version that come with DML before you add other modules. That should remove the breaks. Your own mission would work since you are already using the newest and brightest versions.
  15. Yes - use a ‚changer‘ module, which is a closable pass-through. Run the clone flag through the changer (direct, no change), and it passes all clone signals to the cloner. When all defenders are destroyed and that sets xxx, feed that to the input that closes the changer’s pass-through. From that moment on, clone signals no longer pass, and no more clones can spawn. Simply stack the changer on top of the cloner, and you don’t even need another zone. Perhaps take a look at the ‘gate and switch’ demo and demo doc for other tricks the changer has up its sleeve.
  16. Most likely that's an oversight on my part; I'm not entirely sure what has generated the "Strange Birth" event (are you cloning Objects? That's something I definitely overlooked in testing) which subsequently fails when the script tries to find out which group the object belongs to. Anyway, please try if the script below resolves that issue (simply copy/replace over the existing version). If you have the time, check the new docs, as Recon has received missive updates to its ability. cfxReconMode = {} cfxReconMode.version = "2.0.1" cfxReconMode.verbose = false -- set to true for debug info cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered cfxReconMode.prioList = {} -- group names that are high prio and generate special event cfxReconMode.blackList = {} -- group names that are NEVER detected. Comma separated strings, e.g. {"Always Hidden", "Invisible Group"} cfxReconMode.dynamics = {} -- if a group name is dynamic cfxReconMode.zoneInfo = {} -- additional zone info cfxReconMode.scoutZones = {} -- zones that define aircraft. used for late eval of players cfxReconMode.allowedScouts = {} -- when not using autoscouts cfxReconMode.blindScouts = {} -- to exclude aircraft from being scouts cfxReconMode.removeWhenDestroyed = true cfxReconMode.activeMarks = {} -- all marks and their groups, indexed by groupName cfxReconMode.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course } --[[-- VERSION HISTORY 1.0.0 - initial version 1.0.1 - removeScoutByName() 1.0.2 - garbage collection 1.1.0 - autoRecon - any aircraft taking off immediately signs up, no message when signing up or closing down standalone - copied common procs lerp, agl, dist, distflat from dcsCommon report numbers verbose flag 1.2.0 - queued recons. One scout per second for more even performance removed gc since it's now integrated into update queue removeScout optimization when directly passing name playerOnlyRecon for autoRecon red, blue, grey side filtering on auto scout 1.2.1 - parametrized report sound 1.3.0 - added black list, prio list functionality 1.3.1 - callbacks now also push name, as group can be dead - removed bug when removing dead groups from map 1.4.0 - import dcsCommon, cfxZones etc - added lib check - config zone - prio+ - detect+ 1.4.1 - invocation no longer happen twice for prio. - recon sound - read all flight groups at start to get rid of the - late activation work-around 1.5.0 - removeWhenDestroyed() - autoRemove() - readConfigZone creates default config zone so we get correct defaulting 2.0.0 - DML integration prio+-->prio! detect+ --> detect! and method - changed access to prio and blacklist to hash - dynamic option for prio and black - trigger zones for designating prio and blacklist - reworked stringInList to also include dynamics - Report in SALT format: size, action, loc, time. - Marks add size, action info - LatLon or MGRS - MGRS option in config - filter onEvent for helo and aircraft - allowedScouts and blind - stronger scout filtering at startup - better filtering on startup when autorecon and playeronly - player lazy late checking, zone saving - correct checks when not autorecon - ability to add special flags to recon prio group - event guard in onEvent - <t> wildcard - <lat>, <lon>, <mgrs> wildcards 2.0.1 - getGroup() guard for onEvent(). Objects now seem to birth. cfxReconMode is a script that allows units to perform reconnaissance missions and, after detecting units, marks them on the map with markers for their coalition and some text Also, a callback is initiated for scouts as follows signature: (reason, theSide, theSout, theGroup) with reason a string 'detected' a group was detected 'removed' a mark for a group timed out 'priority' a member of prio group was detected 'start' a scout started scouting 'end' a scout stopped scouting 'dead' a scout has died and was removed from pool theSide - side of the SCOUT that detected units theScout - the scout that detected the group theGroup - the group that is detected theName - the group's name --]]-- cfxReconMode.detectionMinRange = 3000 -- meters at ground level cfxReconMode.detectionMaxRange = 12000 -- meters at max alt (10'000m) cfxReconMode.maxAlt = 9000 -- alt for maxrange (9km = 27k feet) cfxReconMode.autoRecon = true -- add all airborne units, unless cfxReconMode.redScouts = false -- set to false to prevent red scouts in auto mode cfxReconMode.blueScouts = true -- set to false to prevent blue scouts in auto-mode cfxReconMode.greyScouts = false -- set to false to prevent neutral scouts in auto mode cfxReconMode.playerOnlyRecon = false -- only players can do recon cfxReconMode.reportNumbers = true -- also add unit count in report cfxReconMode.prioFlag = nil cfxReconMode.detectFlag = nil cfxReconMode.method = "inc" cfxReconMode.applyMarks = true cfxReconMode.mgrs = false cfxReconMode.ups = 1 -- updates per second. cfxReconMode.scouts = {} -- units that are performing scouting. cfxReconMode.processedScouts = {} -- for managing performance: queue cfxReconMode.detectedGroups = {} -- so we know which have been detected cfxReconMode.marksFadeAfter = 30*60 -- after detection, marks disappear after -- this amount of seconds. -1 means no fade -- 60 is one minute cfxReconMode.callbacks = {} -- sig: cb(reason, side, scout, group) cfxReconMode.uuidCount = 0 -- for unique marks -- end standalone dcsCommon extract function cfxReconMode.uuid() cfxReconMode.uuidCount = cfxReconMode.uuidCount + 1 return cfxReconMode.uuidCount end function cfxReconMode.addCallback(theCB) table.insert(cfxReconMode.callbacks, theCB) end function cfxReconMode.invokeCallbacks(reason, theSide, theScout, theGroup, theName) for idx, theCB in pairs(cfxReconMode.callbacks) do theCB(reason, theSide, theScout, theGroup, theName) end end -- add a priority/blackList group name to prio list function cfxReconMode.addToPrioList(aGroup, dynamic) if not dynamic then dynamic = false end if not aGroup then return end if type(aGroup) == "table" and aGroup.getName then aGroup = aGroup:getName() end if type(aGroup) == "string" then -- table.insert(cfxReconMode.prioList, aGroup) cfxReconMode.prioList[aGroup] = aGroup cfxReconMode.dynamics[aGroup] = dynamic end end function cfxReconMode.addToBlackList(aGroup, dynamic) if not dynamic then dynamic = false end if not aGroup then return end if type(aGroup) == "table" and aGroup.getName then aGroup = aGroup:getName() end if type(aGroup) == "string" then --table.insert(cfxReconMode.blackList, aGroup) cfxReconMode.blackList[aGroup] = aGroup cfxReconMode.dynamics[aGroup] = dynamic end end function cfxReconMode.addToAllowedScoutList(aGroup, dynamic) if not dynamic then dynamic = false end if not aGroup then return end if type(aGroup) == "table" and aGroup.getName then aGroup = aGroup:getName() end if type(aGroup) == "string" then cfxReconMode.allowedScouts[aGroup] = aGroup cfxReconMode.dynamics[aGroup] = dynamic end end function cfxReconMode.addToBlindScoutList(aGroup, dynamic) if not dynamic then dynamic = false end if not aGroup then return end if type(aGroup) == "table" and aGroup.getName then aGroup = aGroup:getName() end if type(aGroup) == "string" then cfxReconMode.blindScouts[aGroup] = aGroup cfxReconMode.dynamics[aGroup] = dynamic end end function cfxReconMode.isStringInList(theString, theList) -- returns two values: inList, and original group name (if exist) if not theString then return false, nil end if type(theString) ~= "string" then return false, nil end if not theList then return false, nil end -- first, try a direct look-up. if this produces a hit -- we directly return true if theList[theString] then return true, theString end -- now try the more involved retrieval with string starts with for idx, aName in pairs(theList) do if dcsCommon.stringStartsWith(theString, aName) then -- they start the same. are dynamics allowed? if cfxReconMode.dynamics[aName] then return true, aName end end end return false, nil end -- addScout directly adds a scout unit. Use from external -- to manually add a unit (e.g. via GUI when autoscout isExist -- off, or to force a scout unit (e.g. when scouts for a side -- are not allowed but you still want a unit from that side -- to scout -- since we use a queue for scouts, also always check the -- processed queue before adding to make sure a scout isn't -- entered multiple times function cfxReconMode.addScout(theUnit) if not theUnit then trigger.action.outText("+++cfxRecon: WARNING - nil Unit on add", 30) return end if type(theUnit) == "string" then local u = Unit.getByName(theUnit) theUnit = u end if not theUnit then trigger.action.outText("+++cfxRecon: WARNING - did not find unit on add", 30) return end if not theUnit:isExist() then return end -- find out if this an update or a new scout local thisID = tonumber(theUnit:getID()) local theName = theUnit:getName() local lastUnit = cfxReconMode.scouts[theName] local isProcced = false -- may also be in procced line if not lastUnit then lastUnit = cfxReconMode.processedScouts[theName] if lastUnit then isProcced = true end end if lastUnit then -- this is merely an overwrite if cfxReconMode.verbose then trigger.action.outText("+++rcn: UPDATE scout " .. theName .. " -- no CB invoke", 30) end else if cfxReconMode.verbose then trigger.action.outText("+++rcn: new scout " .. theName .. " with ID " .. thisID, 30) end -- a new scout! Invoke callbacks local scoutGroup = theUnit:getGroup() local theSide = scoutGroup:getCoalition() cfxReconMode.invokeCallbacks("start", theSide, theUnit, nil, "<none>") end if isProcced then -- overwrite exiting entry in procced queue cfxReconMode.processedScouts[theName] = theUnit else -- add / overwrite into normal queue cfxReconMode.scouts[theName] = theUnit end if cfxReconMode.verbose then trigger.action.outText("+++rcn: addded scout " .. theUnit:getName(), 30) end end function cfxReconMode.removeScout(theUnit) if not theUnit then trigger.action.outText("+++rcn: WARNING - nil Unit on remove", 30) return end if type(theUnit) == "string" then cfxReconMode.removeScoutByName(theUnit) return end if not theUnit then return end if not theUnit:isExist() then return end cfxReconMode.removeScoutByName(theUnit:getName()) local scoutGroup = theUnit:getGroup() local theSide = scoutGroup:getCoalition() cfxReconMode.invokeCallbacks("end", theSide, theUnit, nil, "<none>") end -- warning: removeScoutByName does NOT invoke callbacks, always -- use removeScout instead! function cfxReconMode.removeScoutByName(aName) cfxReconMode.scouts[aName] = nil cfxReconMode.processedScouts[aName] = nil -- also remove from processed stack if cfxReconMode.verbose then trigger.action.outText("+++rcn: removed scout " .. aName, 30) end end function cfxReconMode.canDetect(scoutPos, theGroup, visRange) -- determine if a member of theGroup can be seen from -- scoutPos at visRange -- returns true and pos when detected local allUnits = theGroup:getUnits() for idx, aUnit in pairs(allUnits) do if aUnit:isExist() and aUnit:getLife() >= 1 then local uPos = aUnit:getPoint() uPos.y = uPos.y + 3 -- raise my 3 meters local d = dcsCommon.distFlat(scoutPos, uPos) if d < visRange then -- is in visual range. do we have LOS? if land.isVisible(scoutPos, uPos) then -- group is visible, stop here, return true return true, uPos end else -- OPTIMIZATION: if a unit is outside -- detect range, we assume that entire group -- is, since they are bunched together -- edge cases may get lucky tests return false, nil end end end return false, nil -- nothing visible end function cfxReconMode.placeMarkForUnit(location, theSide, theGroup) local theID = cfxReconMode.uuid() local theDesc = "Contact: "..theGroup:getName() if cfxReconMode.reportNumbers then -- theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)" theDesc = theDesc .. " - " .. cfxReconMode.getSit(theGroup) .. ", " .. cfxReconMode.getAction(theGroup) .. "." end trigger.action.markToCoalition( theID, theDesc, location, theSide, false, nil) return theID end function cfxReconMode.removeMarkForArgs(args) local theSide = args[1] local theScout = args[2] local theGroup = args[3] local theID = args[4] local theName = args[5] -- only remove if it wasn't already removed. -- this method is called async *and* sync! if cfxReconMode.activeMarks[theName] then trigger.action.removeMark(theID) -- invoke callbacks cfxReconMode.invokeCallbacks("removed", theSide, theScout, theGroup, theName) cfxReconMode.activeMarks[theName] = nil -- also remove from list of groups being checked end cfxReconMode.detectedGroups[theName] = nil -- some housekeeping. end function cfxReconMode.getSit(theGroup) local msg = "" -- analyse the group we just discovered. We know it's a ground troop, so simply differentiate between vehicles and infantry local theUnits = theGroup:getUnits() local numInf = 0 local numVehicles = 0 for idx, aUnit in pairs(theUnits) do if dcsCommon.unitIsInfantry(aUnit) then numInf = numInf + 1 else numVehicles = numVehicles + 1 end end if numInf > 0 and numVehicles > 0 then -- mixed infantry and vehicles msg = numInf .. " infantry and " .. numVehicles .. " vehicles" elseif numInf > 0 then -- only infantry msg = numInf .. " infantry" else -- only vehicles msg = numVehicles .. " vehicles" end return msg end function cfxReconMode.getAction(theGroup) local msg = "" -- simply get the first unit and get velocity vector. -- if it's smaller than 1 m/s (= 3.6 kmh), it's "Guarding", if it's faster, it's -- moving with direction local theUnit = theGroup:getUnit(1) local vvel = theUnit:getVelocity() local vel = dcsCommon.vMag(vvel) if vel < 1 then msg = "apparently guarding" else local speed = "" if vel < 3 then speed = "slowly" elseif vel < 6 then speed = "deliberately" else speed = "briskly" end local heading = dcsCommon.getUnitHeading(theUnit) -- in rad msg = speed .. " moving " .. dcsCommon.bearing2compass(heading) end return msg end function cfxReconMode.getLocation(theGroup) local msg = "" local theUnit = theGroup:getUnit(1) local currPoint = theUnit:getPoint() if cfxReconMode.mgrs then local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint)) msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing else local lat, lon, alt = coord.LOtoLL(currPoint) lat, lon = dcsCommon.latLon2Text(lat, lon) msg = "Lat " .. lat .. " Lon " .. lon end return msg end function cfxReconMode.getTimeData() local msg = "" local absSecs = timer.getAbsTime()-- + env.mission.start_time while absSecs > 86400 do absSecs = absSecs - 86400 -- subtract out all days end msg = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs) return "at " .. msg end function cfxReconMode.generateSALT(theScout, theGroup) local msg = theScout:getName() .. " reports new ground contact " .. theGroup:getName() .. ":\n" -- SALT: S = Situation or number of units A = action they are doing L = Location T = Time msg = msg .. cfxReconMode.getSit(theGroup) .. ", "-- S msg = msg .. cfxReconMode.getAction(theGroup) .. ", " -- A msg = msg .. cfxReconMode.getLocation(theGroup) .. ", " -- L msg = msg .. cfxReconMode.getTimeData() -- T return msg end function cfxReconMode.processZoneMessage(inMsg, theZone) if not inMsg then return "<nil inMsg>" end local formerType = type(inMsg) if formerType ~= "string" then inMsg = tostring(inMsg) end if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end local outMsg = "" -- replace line feeds outMsg = inMsg:gsub("<n>", "\n") if theZone then outMsg = outMsg:gsub("<z>", theZone.name) end -- replace <t> with current mission time HMS local absSecs = timer.getAbsTime()-- + env.mission.start_time while absSecs > 86400 do absSecs = absSecs - 86400 -- subtract out all days end local timeString = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs) outMsg = outMsg:gsub("<t>", timeString) -- replace <lat> with lat of zone point and <lon> with lon of zone point -- and <mgrs> with mgrs coords of zone point local currPoint = cfxZones.getPoint(theZone) local lat, lon, alt = coord.LOtoLL(currPoint) lat, lon = dcsCommon.latLon2Text(lat, lon) outMsg = outMsg:gsub("<lat>", lat) outMsg = outMsg:gsub("<lon>", lon) currPoint = cfxZones.getPoint(theZone) local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint)) local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing outMsg = outMsg:gsub("<mgrs>", mgrs) return outMsg end function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc) -- put a mark on the map if cfxReconMode.applyMarks then local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup) local gName = theGroup:getName() local args = {mySide, theScout, theGroup, theID, gName} cfxReconMode.activeMarks[gName] = args -- schedule removal if desired if cfxReconMode.marksFadeAfter > 0 then timer.scheduleFunction(cfxReconMode.removeMarkForArgs, args, timer.getTime() + cfxReconMode.marksFadeAfter) end end -- say something if cfxReconMode.announcer then local msg = cfxReconMode.generateSALT(theScout, theGroup) trigger.action.outTextForCoalition(mySide, msg, 30) -- trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30) if cfxReconMode.verbose then trigger.action.outText("+++rcn: announced for side " .. mySide, 30) end -- play a sound trigger.action.outSoundForCoalition(mySide, cfxReconMode.reconSound) else end -- see if it was a prio target local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList) if inList then -- if cfxReconMode.announcer then if cfxReconMode.verbose then trigger.action.outText("+++rcn: Priority target spotted", 30) end -- invoke callbacks cfxReconMode.invokeCallbacks("priority", mySide, theScout, theGroup, theGroup:getName()) -- increase prio flag if cfxReconMode.prioFlag then cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone) end -- see if we were passed additional info in zInfo if gName and cfxReconMode.zoneInfo[gName] then local zInfo = cfxReconMode.zoneInfo[gName] if zInfo.prioMessage then -- prio message displays even when announcer is off local msg = zInfo.prioMessage msg = cfxReconMode.processZoneMessage(msg, zInfo.theZone) trigger.action.outTextForCoalition(mySide, msg, 30) if cfxReconMode.verbose or zInfo.theZone.verbose then trigger.action.outText("+++rcn: prio message sent for prio target zone <" .. zInfo.theZone.name .. ">",30) end end if zInfo.theFlag then cfxZones.pollFlag(zInfo.theFlag, cfxReconMode.method, zInfo.theZone) if cfxReconMode.verbose or zInfo.theZone.verbose then trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30) end end end else -- invoke callbacks cfxReconMode.invokeCallbacks("detected", mySide, theScout, theGroup, theGroup:getName()) -- increase normal flag if cfxReconMode.detectFlag then cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone) end end end function cfxReconMode.performReconForUnit(theScout) if not theScout then return end if not theScout:isExist() then return end -- will be gc'd soon -- get altitude above ground to calculate visual range local alt = dcsCommon.getUnitAGL(theScout) local visRange = dcsCommon.lerp(cfxReconMode.detectionMinRange, cfxReconMode.detectionMaxRange, alt/cfxReconMode.maxAlt) local scoutPos = theScout:getPoint() -- figure out which groups we are looking for local myCoal = theScout:getCoalition() local enemyCoal = 1 if myCoal == 1 then enemyCoal = 2 end -- iterate all enemy units until we find one -- and then stop this iteration (can only detect one -- group per pass) local enemyGroups = coalition.getGroups(enemyCoal) for idx, theGroup in pairs (enemyGroups) do -- make sure it's a ground unit local isGround = theGroup:getCategory() == 2 if theGroup:isExist() and isGround then local visible, location = cfxReconMode.canDetect(scoutPos, theGroup, visRange) if visible then -- see if we already detected this one local groupName = theGroup:getName() if cfxReconMode.detectedGroups[groupName] == nil then -- only now check against blackList local inList, gName = cfxReconMode.isStringInList(groupName, cfxReconMode.blackList) if not inList then -- visible and not yet seen -- perhaps add some percent chance now -- remember that we know this group cfxReconMode.detectedGroups[groupName] = theGroup cfxReconMode.detectedGroup(myCoal, theScout, theGroup, location) return -- stop, as we only detect one group per pass end end end end end end function cfxReconMode.updateQueues() -- schedule next call timer.scheduleFunction(cfxReconMode.updateQueues, {}, timer.getTime() + 1/cfxReconMode.ups) -- we only process the first aircraft in -- the scouts array, move it to processed and then shrink -- scouts table until it's empty. When empty, transfer all -- back and start cycle anew local theFocusScoutName = nil local procCount = 0 -- no iterations done yet for name, scout in pairs(cfxReconMode.scouts) do theFocusScoutName = name -- remember so we can delete if not scout:isExist() then -- we ignore the scout, and it's -- forgotten since no longer transferred -- i.e. built-in GC if cfxReconMode.verbose then trigger.action.outText("+++rcn: GC - removing scout " .. name .. " because it no longer exists", 30) end -- invoke 'end' for this scout cfxReconMode.invokeCallbacks("dead", -1, nil, nil, name) else -- scan for this scout cfxReconMode.performReconForUnit(scout) -- move it to processed table cfxReconMode.processedScouts[name] = scout end procCount = 1 -- remember we went through one iteration break -- always end after first iteration end -- remove processed scouts from scouts array if procCount > 0 then -- we processed one scout (even if scout itself did not exist) -- remove that scout from active scouts table cfxReconMode.scouts[theFocusScoutName] = nil else -- scouts is empty. copy processed table back to scouts -- restart scouts array, contains GC already cfxReconMode.scouts = cfxReconMode.processedScouts cfxReconMode.processedScouts = {} -- start new empty processed queue end end function cfxReconMode.isGroupStillAlive(gName) local theGroup = Group.getByName(gName) if not theGroup then return false end if not theGroup:isExist() then return false end local allUnits = theGroup:getUnits() for idx, aUnit in pairs (allUnits) do if aUnit:getLife() >= 1 then return true end end return false end function cfxReconMode.autoRemove() -- schedule next call timer.scheduleFunction(cfxReconMode.autoRemove, {}, timer.getTime() + 1/cfxReconMode.ups) local toRemove = {} -- scan all marked groups, and when they no longer exist, remove them for idx, args in pairs (cfxReconMode.activeMarks) do -- args = {mySide, theScout, theGroup, theID, gName} local gName = args[5] if not cfxReconMode.isGroupStillAlive(gName) then -- remove mark, remove group from set table.insert(toRemove, args) end end for idx, args in pairs(toRemove) do cfxReconMode.removeMarkForArgs(args) trigger.action.outText("+++recn: removed mark: " .. args[5], 30) end end -- late eval player function cfxReconMode.lateEvalPlayerUnit(theUnit) -- check if a player is inside one of the scout zones -- first: quick check if the player is already in a list local aGroup = theUnit:getGroup() local gName = aGroup:getName() if cfxReconMode.allowedScouts[gName] then return end if cfxReconMode.blindScouts[gName] then return end -- get location local p = theUnit:getPoint() -- iterate all scoutZones for idx, theZone in pairs (cfxReconMode.scoutZones) do local isScout = theZone.isScout local dynamic = theZone.dynamic local inZone = cfxZones.pointInZone(p, theZone) if inZone then if isScout then cfxReconMode.addToAllowedScoutList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to allowed scouts", 30) else trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to allowed scouts", 30) end end else cfxReconMode.addToBlindScoutList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to BLIND scouts list", 30) else trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to BLIND scouts list", 30) end end end return -- we stop after first found end end end -- event handler function cfxReconMode:onEvent(event) if not event then return end if not event.initiator then return end if not (event.id == 15 or event.id == 3) then return end local theUnit = event.initiator if not theUnit:isExist() then return end if not theUnit.getGroup then -- strange, but seemingly can happen return end local theGroup = theUnit:getGroup() -- trigger.action.outText("+++rcn-ENTER onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30) if not theGroup then return end local gCat = theGroup:getCategory() -- only continue if cat = 0 (aircraft) or 1 (helo) if gCat > 1 then return end -- we simply add scouts as they are garbage-collected -- every so often when they do not exist if event.id == 15 or -- birth event.id == 3 -- take-off. should already have been taken -- care of by birth, but you never know then -- check if a side must not have scouts. -- this will prevent player units to auto- -- scout when they are on that side. in that case -- you must add manually local theSide = theUnit:getCoalition() local isPlayer = theUnit:getPlayerName() if isPlayer then -- since players wake up late, we lazy-eval their group -- and add it to the blind/scout lists cfxReconMode.lateEvalPlayerUnit(theUnit) if cfxReconMode.verbose then trigger.action.outText("+++rcn: late player check complete for <" .. theUnit:getName() .. ">", 30) end else isPlayer = false -- safer than sorry end if cfxReconMode.autoRecon then if theSide == 0 and not cfxReconMode.greyScouts then return -- grey scouts are not allowed end if theSide == 1 and not cfxReconMode.redScouts then return -- grey scouts are not allowed end if theSide == 2 and not cfxReconMode.blueScouts then return -- grey scouts are not allowed end if cfxReconMode.playerOnlyRecon then if not isPlayer then if cfxReconMode.verbose then trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: no player unit", 30) end return -- only players can do recon. this unit is AI end end end -- check if cfxReconMode.autoRecon is enabled -- otherwise, abort the aircraft is not in -- scourlist local gName = theGroup:getName() if not cfxReconMode.autoRecon then -- no auto-recon. plane must be in scouts list local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts) if not inList then if cfxReconMode.verbose then trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: not in scout list", 30) end return end end -- check if aircraft is in blindlist -- abort if so local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts) if inList then if cfxReconMode.verbose then trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: unit cannot scout", 30) end return end if cfxReconMode.verbose then trigger.action.outText("+++rcn: event " .. event.id .. " for unit " .. theUnit:getName(), 30) end cfxReconMode.addScout(theUnit) end -- trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30) end -- -- read all existing planes -- function cfxReconMode.processScoutGroups(theGroups) for idx, aGroup in pairs(theGroups) do -- process all planes in that group -- we are very early in the mission, only few groups really -- exist now, the rest of the units come in with 15 event if aGroup:isExist() then -- see if we want to add these aircraft to the -- active scout list local gName = aGroup:getName() local isBlind, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts) local isScout, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts) local doAdd = cfxReconMode.autoRecon if cfxReconMode.autoRecon then local theSide = aGroup:getCoalition() if theSide == 0 and not cfxReconMode.greyScouts then doAdd = false elseif theSide == 1 and not cfxReconMode.redScouts then doAdd = false elseif theSide == 2 and not cfxReconMode.blueScouts then doAdd = false end end if isBlind then doAdd = false end if isScout then doAdd = true end -- overrides all if doAdd then local allUnits = Group.getUnits(aGroup) for idy, aUnit in pairs (allUnits) do if aUnit:isExist() then if cfxReconMode.autoRecon and cfxReconMode.playerOnlyRecon and (aUnit:getPlayerName() == nil) then if cfxReconMode.verbose then trigger.action.outText("+++rcn: skipped unit " ..aUnit:getName() .. " because not player unit", 30) end else cfxReconMode.addScout(aUnit) if cfxReconMode.verbose then trigger.action.outText("+++rcn: added unit " ..aUnit:getName() .. " to pool at startup", 30) end end end end else if cfxReconMode.verbose then trigger.action.outText("+++rcn: filtered group " .. gName .. " from being entered into scout pool at startup", 30) end end end end end function cfxReconMode.initScouts() -- get all groups of aircraft. Unrolled loop 0..2 -- added helicopters, removed check for grey/red/bluescouts, -- as that happens in processScoutGroups local theAirGroups = {} theAirGroups = coalition.getGroups(0, 0) -- 0 = aircraft cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(0, 1) -- 1 = helicopter cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(1, 0) -- 0 = aircraft cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(1, 1) -- 1 = helicopter cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(2, 0) -- 0 = aircraft cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(2, 1) -- 1 = helicopter cfxReconMode.processScoutGroups(theAirGroups) end -- -- read config -- function cfxReconMode.readConfigZone() -- note: must match exactly!!!! local theZone = cfxZones.getZoneByName("reconModeConfig") if not theZone then if cfxReconMode.verbose then trigger.action.outText("+++rcn: no config zone!", 30) end theZone = cfxZones.createSimpleZone("reconModeConfig") else if cfxReconMode.verbose then trigger.action.outText("+++rcn: found config zone!", 30) end end cfxReconMode.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) cfxReconMode.autoRecon = cfxZones.getBoolFromZoneProperty(theZone, "autoRecon", true) cfxReconMode.redScouts = cfxZones.getBoolFromZoneProperty(theZone, "redScouts", false) cfxReconMode.blueScouts = cfxZones.getBoolFromZoneProperty(theZone, "blueScouts", true) cfxReconMode.greyScouts = cfxZones.getBoolFromZoneProperty(theZone, "greyScouts", false) cfxReconMode.playerOnlyRecon = cfxZones.getBoolFromZoneProperty(theZone, "playerOnlyRecon", false) cfxReconMode.reportNumbers = cfxZones.getBoolFromZoneProperty(theZone, "reportNumbers", true) cfxReconMode.detectionMinRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMinRange", 3000) cfxReconMode.detectionMaxRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMaxRange", 12000) cfxReconMode.maxAlt = cfxZones.getNumberFromZoneProperty(theZone, "maxAlt", 9000) if cfxZones.hasProperty(theZone, "prio+") then cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none") elseif cfxZones.hasProperty(theZone, "prio!") then cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio!", "*<none>") end if cfxZones.hasProperty(theZone, "detect+") then cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none") elseif cfxZones.hasProperty(theZone, "detect!") then cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect!", "*<none>") end cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") if cfxZones.hasProperty(theZone, "reconMethod") then cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "reconMethod", "inc") end cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true) cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) -- trigger.action.outText("recon: announcer is " .. dcsCommon.bool2Text(cfxReconMode.announcer), 30) -- announced if cfxZones.hasProperty(theZone, "reconSound") then cfxReconMode.reconSound = cfxZones.getStringFromZoneProperty(theZone, "reconSound", "<nosound>") end cfxReconMode.removeWhenDestroyed = cfxZones.getBoolFromZoneProperty(theZone, "autoRemove", true) cfxReconMode.mgrs = cfxZones.getBoolFromZoneProperty(theZone, "mgrs", false) cfxReconMode.theZone = theZone -- save this zone end -- -- read blackList and prio list groups -- function cfxReconMode.processReconZone(theZone) local theList = cfxZones.getStringFromZoneProperty(theZone, "recon", "prio") theList = string.upper(theList) local isBlack = dcsCommon.stringStartsWith(theList, "BLACK") local zInfo = {} zInfo.theZone = theZone zInfo.isBlack = isBlack if cfxZones.hasProperty(theZone, "spotted!") then zInfo.theFlag = cfxZones.getStringFromZoneProperty(theZone, "spotted!", "*<none>") end if cfxZones.hasProperty(theZone, "prioMessage") then zInfo.prioMessage = cfxZones.getStringFromZoneProperty(theZone, "prioMessage", "<none>") end local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic", false) zInfo.dynamic = dynamic local categ = 2 -- ground troops only local allGroups = cfxZones.allGroupsInZone(theZone, categ) for idx, aGroup in pairs(allGroups) do local gName = aGroup:getName() cfxReconMode.zoneInfo[gName] = zInfo if isBlack then cfxReconMode.addToBlackList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to blacklist", 30) else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to blacklist", 30) end end else cfxReconMode.addToPrioList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to priority target list", 30) else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to priority target list", 30) end end end end end function cfxReconMode.processScoutZone(theZone) local isScout = cfxZones.getBoolFromZoneProperty(theZone, "scout", true) local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic") theZone.dynamic = dynamic theZone.isScout = isScout local categ = 0 -- aircraft local allFixed = cfxZones.allGroupsInZone(theZone, categ) local categ = 1 -- helos local allRotor = cfxZones.allGroupsInZone(theZone, categ) local allGroups = dcsCommon.combineTables(allFixed, allRotor) for idx, aGroup in pairs(allGroups) do if isScout then cfxReconMode.addToAllowedScoutList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to allowed scouts", 30) else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to allowed scouts", 30) end end else cfxReconMode.addToBlindScoutList(aGroup, dynamic) if cfxReconMode.verbose or theZone.verbose then if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to BLIND scouts list", 30) else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to BLIND scouts list", 30) end end end end table.insert(cfxReconMode.scoutZones, theZone) end function cfxReconMode.readReconGroups() local attrZones = cfxZones.getZonesWithAttributeNamed("recon") for k, aZone in pairs(attrZones) do cfxReconMode.processReconZone(aZone) end end function cfxReconMode.readScoutGroups() local attrZones = cfxZones.getZonesWithAttributeNamed("scout") for k, aZone in pairs(attrZones) do cfxReconMode.processScoutZone(aZone) end end -- -- start -- function cfxReconMode.start() -- lib check if not dcsCommon.libCheck("cfx Recon Mode", cfxReconMode.requiredLibs) then return false end -- read config cfxReconMode.readConfigZone() -- gather prio and blacklist groups cfxReconMode.readReconGroups() -- gather allowed and forbidden scouts cfxReconMode.readScoutGroups() -- gather exiting planes cfxReconMode.initScouts() -- start update cycle cfxReconMode.updateQueues() -- if dead groups are removed from map, -- schedule housekeeping if cfxReconMode.removeWhenDestroyed then cfxReconMode.autoRemove() end if true or cfxReconMode.autoRecon then -- install own event handler to detect -- when a unit takes off and add it to scout -- roster world.addEventHandler(cfxReconMode) end trigger.action.outText("cfx Recon version " .. cfxReconMode.version .. " started.", 30) return true end -- -- test callback -- function cfxReconMode.demoReconCB(reason, theSide, theScout, theGroup, theName) trigger.action.outText("recon CB: " .. reason .. " -- " .. theScout:getName() .. " spotted " .. theName, 30) end if not cfxReconMode.start() then cfxReconMode = nil end -- debug: wire up my own callback -- cfxReconMode.addCallback(cfxReconMode.demoReconCB) --[[-- ideas: - renew lease. when already sighted, simply renew lease, maybe update location. - update marks and renew lease TODO: red+ and blue+ - flags to increase when a plane of the other side is detected TODO: recon: scout and blind for aircraft in group to add / remove scouts, maybe use scout keyword allow special bangs per priority group --]]--
  17. Huh. Now that we the Falklands, the Vulcan, of course. Never mind that Operation Black Buck amounted to little more than a stunt - there simply is no plane that looks more graceful, and I'll take any pretext to have that plane (even if only AI) in DCS.
  18. Version 1.0 - 20220616 'nuff said. Changes/Updates: Demos - DML Intro - Artillery Recon (new, dev) - Missile evasion - Recon Mode Reloaded (new) Modules - reconMode 2.0.0 - messenger: 1.3.2 - dcsCommon: 2.6.5 - artillery zones 2.2.1 - Guardian Angel 3.0.0 - radio Menus 1.0.1
  19. While I'm not sure what the "Target Detected" script requires, you can get the currently connected player units for each side with coalition.getPlayers(coalitionID) See here
  20. I've completed some preliminary testing. It's not pretty as DCS does not allow scripts access the current task in the controller, but I've put some simple SALT reporting into recon mode, and added a LOT of QoL to bring this module up to DML standard. It may even make it into 1.0 This how the report looks in the message log. Coords can be in LatLon or MGRS. And this is how it looks on the map marker (obviously it does not require coordinates).
  21. Not to be too much of a smart-ass, but saving a game state never was an issue. That’s easy. Loading it in a meaningful way is, probably due to the way how missions are loaded. Units in DCS can only spawn at 100 percent health, at the beginning of their order phase, fully supplied etc. Meaning that DCS currently has no concept of unit status (other than full health, comfortable). Heck, we can’t even change the time of day or weather. In that regard, DCS is lagging the competition by a good decade or two. We need a good loader, or post-start status loader to be able to impose a status on a mission that started in status X and now has progressed to Y. Here’s to hoping this will change.
  22. Because loading low res textures when close to the ground would make it an even worse experience. You can get away with low res textures when far away, but not when you are close and can see the texture in all detail. That being said, most graphics engine usually load textures in multiple resolutions already and dynamically switch to the one best suited for distance; same with object detail (which has an even greater impact on performance than textures)
  23. Version 0.9995 - 202206029 Featured Updates: Radio Menu So maybe - just maybe - one of the most helpful modules arrives dead last. I'm in the process of finishing off my laundry list of useful modules for the dot-oh send-off. Belatedly I now realize that "radio menu" should have been made available much earlier: it resolves so many problems for budding mission builders, is easy to use, and gives access to mission functions that are difficult to do outside of Lua. Well, it's here now. For what it's worth, as for coming in next to last, it profits from the full spectrum of DML abilities from the get-go. 'RadioMenu' can add DML-style "F10 Other..." communication menus (with up to four items/commands per menu) with integrated flag handling (multiple activations possible of course), and built-in cooldown handling to boot. So, adding Radio Menus (as opposed to simply commands) has never been easier. And yeah, you read that right: built-in cool-down handling. How cool is that? The final late-joiner is 'delicates', a module that makes units and static objects 'delicate' to the touch: as soon as they are damaged, they explode. Use them for harrowing, nail-biting escort missions, or to create spectacular effects when raiding a base. Finally, another QoL update has arrived: messenger gained the ability to interpret the value flag as time and format it the same way a the cooldown message for menus All Changes in detail: - Documentation - RadioMenu module doc - Delicates module doc - Reinforcements A La Carte demo doc - Delicate Subjects demo doc - Endless Looping Spawns demo doc - messenger HMS format update - Demos - endless looping spawns (new) - delicate subjects (new) - Reinforcements A La Carte (new) - Modules - cfxZones 2.7.9 - delicates 1.0.0 - radiomenu 1.0.0 - dcsCommon 2.6.4 - messenger 1.3.1 - wiper 1.1.0 Enjoy, -ch
  24. Ah, well, that’s something I could look into - maybe not fully fledged SALUTE, but perhaps a SALT variant. May have to wait until after 1.0, though.
  25. Taking off in the Huey from Ushuaia, on a 340 heading, I get object drop outs (all buildings) for one eye (switches to other eye when I turn my head) while overflying town, same happens in a Hornet. Performance is so-so in on a 2080Ti with a Vive Pro 2 This map works in VR, but IMHO I have better performance in Syria (Haifa) than Chile. Lighting reminds me of home (I hail from the North), very nice. Map still needs a lot of work, but I'm glad I have it, definitely want to support this project.
  • Create New...