diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 0233912..f389eed 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 f53f78e..e2c529e 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/asw.lua b/modules/asw.lua index b6bfb70..c4cc1ba 100644 --- a/modules/asw.lua +++ b/modules/asw.lua @@ -1,5 +1,5 @@ asw = {} -asw.version = "1.0.0" +asw.version = "1.0.1" asw.verbose = false asw.requiredLibs = { "dcsCommon", -- always @@ -15,6 +15,7 @@ asw.fixes = {} -- all subs that we have a fix on. indexed by sub name --[[-- Version History 1.0.0 - initial version + 1.0.1 - integration with playerScore --]]-- @@ -36,6 +37,10 @@ function asw.createTorpedoForUnit(theUnit) local t = asw.createTorpedo() t.coalition = theUnit:getCoalition() t.point = theUnit:getPoint() + t.droppedBy = theUnit + if theUnit.getPlayerName and theUnit:getPlayerName() ~= nil then + t.playerName = theUnit:getPlayerName() + end return t end @@ -716,6 +721,7 @@ function asw.updateTorpedo(theTorpedo, allSubs) end if dist < 1.2 * displacement then + theTorpedo.target = theSub theTorpedo.state = 99 -- go boom end markTorpedo(theTorpedo) @@ -762,10 +768,26 @@ function asw.updateTorpedo(theTorpedo, allSubs) return true elseif theTorpedo.state == 99 then -- go boom if Unit.isExist(theTorpedo.target) then + if asw.verbose then + trigger.action.outText("99 torpedoes have target", 30) + end + + -- interface to playerScore + if cfxPlayerScore then + asw.doScore(theTorpedo) + else + if asw.verbose then + trigger.action.outText("No playerScore present", 30) + end + end Unit.destroy(theTorpedo.target) + else + if asw.verbose then + trigger.action.outText("t99 no target exist", 30) + end end -- impact! - trigger.action.outTextForCoalition(theTorpedo.coalition, "Impact for " .. theTorpedo.name .. "! We have confirmed hit on submerged contact!", 30) + trigger.action.outTextForCoalition(theTorpedo.coalition, "Impact for " .. theTorpedo.name .. "! We have confirmed hit on submerged contact!", 30) if theTorpedo.coalition == 1 then if asw.redKill then cfxZones.pollFlag(asw.redKill, asw.method, asw) @@ -796,6 +818,86 @@ function asw.updateTorpedo(theTorpedo, allSubs) return true end +-- PlayerScore interface +function processFeat(inMsg, playerUnit, victim, timeFormat) + if not inMsg then return "" end + -- replace 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 + if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end + -- + local timeString = dcsCommon.processHMS(timeFormat, absSecs) + local outMsg = inMsg:gsub("", timeString) + -- + outMsg = dcsCommon.processStringWildcards(outMsg) -- + -- + outMsg = cfxPlayerScore.preprocessWildcards(outMsg, playerUnit, victim) + return outMsg +end + +function asw.doScore(theTorpedo) + if asw.verbose then + trigger.action.outText("asw: enter doScore", 30) + end + -- make sure that this is a player-dropped torpedo + if not theTorpedo then + if asw.verbose then + trigger.action.outText("no torpedo", 30) + end + return + end + local theUnit = theTorpedo.target + if not theTorpedo.playerName then + if asw.verbose then + trigger.action.outText("no torpedo", 30) + end + return + end + local pName = theTorpedo.playerName + -- make sure that the player's original unit still exists + if not (theTorpedo.droppedBy and Unit.isExist(theTorpedo.droppedBy)) then + if asw.verbose then + trigger.action.outText("torpedo dropper dead", 30) + end + return -- torpedo-dropping unit did not survive + end + + local fratricide = (theTorpedo.coalition == theUnit:getCoalition()) + if fratricide then + if asw.verbose then + trigger.action.outText("+++asw: fratricide detected", 30) + end + end + + if asw.killScore > 0 then + -- award score + local score = asw.killScore + if fratricide then score = -1 * score end + cfxPlayerScore.logKillForPlayer(pName, theUnit) + cfxPlayerScore.awardScoreTo(theTorpedo.coalition, score, pName) + if asw.verbose then + trigger.action.outText("updated score (" .. score .. ") for player <" .. pName .. ">", 30) + end + else + if asw.verbose then + trigger.action.outText("no score num defined", 30) + end + end + + if asw.killFeat and (not fratricide) then + -- we treat killFeat as boolean + local theFeat = "Killed type submerged vessel at " + theFeat = processFeat(theFeat, theTorpedo.droppedBy, theUnit) + cfxPlayerScore.logFeatForPlayer(pName, theFeat) + else + if asw.verbose then + trigger.action.outText("no feat defined or fratricide", 30) + end + end +end + -- -- MAIN UPDATE -- @@ -940,10 +1042,17 @@ function asw.readConfigZone() asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red") asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor) + + asw.killScore = cfxZones.getNumberFromZoneProperty(theZone, "killScore", 0) + + if cfxZones.hasProperty(theZone, "killFeat") then + asw.killFeat = cfxZones.getStringFromZoneProperty(theZone, "killFeat", "Sub Kill") + end if asw.verbose then trigger.action.outText("+++asw: read config", 30) end + end function asw.start() diff --git a/modules/aswGUI.lua b/modules/aswGUI.lua index 5d34f8e..7b67b57 100644 --- a/modules/aswGUI.lua +++ b/modules/aswGUI.lua @@ -1,5 +1,5 @@ aswGUI = {} -aswGUI.version = "1.0.1" +aswGUI.version = "1.0.2" aswGUI.verbose = false aswGUI.requiredLibs = { "dcsCommon", -- always @@ -12,6 +12,7 @@ aswGUI.requiredLibs = { Version History 1.0.0 - initial version 1.0.1 - env.info clean-up, verbosity clean-up + 1.0.2 - late start capability --]]-- @@ -524,6 +525,22 @@ function aswGUI:onEvent(theEvent) end end +function aswGUI.processPlayerUnit(theUnit) + local name = theUnit:getName() + local conf = aswGUI.aswCraft[name] + if not conf then + -- let's init it + conf = aswGUI.initUnit(name) + aswGUI.aswCraft[name] = conf + else + aswGUI.resetConf(conf) + end + aswGUI.setMenuForUnit(theUnit) + if aswGUI.verbose then + trigger.action.outText("aswG: set up player <" .. theUnit:getPlayerName() .. "> in <" .. name .. ">", 30) + end +end + -- -- Config & start -- @@ -571,7 +588,10 @@ function aswGUI.start() -- subscribe to world events world.addEventHandler(aswGUI) - + + -- install menus in all existing players + dcsCommon.iteratePlayers(aswGUI.processPlayerUnit) + -- say Hi trigger.action.outText("cfx ASW GUI v" .. aswGUI.version .. " started.", 30) return true diff --git a/modules/cfxPlayerScore.lua b/modules/cfxPlayerScore.lua index d6682c0..e691ae9 100644 --- a/modules/cfxPlayerScore.lua +++ b/modules/cfxPlayerScore.lua @@ -1,5 +1,5 @@ cfxPlayerScore = {} -cfxPlayerScore.version = "2.0.0" +cfxPlayerScore.version = "2.0.1" cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav" @@ -70,7 +70,9 @@ cfxPlayerScore.firstSave = true -- to force overwrite - pkMod attribute - pvp feat - immediate awarding of all negative scores, even if deferred - + 2.0.1 - corrected access to nowString() + - more robust config reading + --]]-- cfxPlayerScore.requiredLibs = { @@ -1278,7 +1280,7 @@ function cfxPlayerScore.saveScores(theText, name) end -- prepend time for score - theText = "\n\n====== Mission Time: " .. dcsCommon.nowstring() .. "\n" .. theText + theText = "\n\n====== Mission Time: " .. dcsCommon.nowString() .. "\n" .. theText end if persistence.saveText(theText, name, shared, append) then @@ -1360,10 +1362,11 @@ function cfxPlayerScore.start() local theZone = cfxZones.getZoneByName("playerScoreConfig") if not theZone then trigger.action.outText("+++scr: no config!", 30) - else - cfxPlayerScore.readConfigZone(theZone) - trigger.action.outText("+++scr: read config", 30) + theZone = cfxZones.createSimpleZone("playerScoreConfig") end + cfxPlayerScore.readConfigZone(theZone) + -- trigger.action.outText("+++scr: read config", 30) + -- read all scoreSafe zones local safeZones = cfxZones.zonesWithProperty("scoreSafe") diff --git a/modules/cfxPlayerScoreUI.lua b/modules/cfxPlayerScoreUI.lua index 65309a6..47831f0 100644 --- a/modules/cfxPlayerScoreUI.lua +++ b/modules/cfxPlayerScoreUI.lua @@ -1,14 +1,15 @@ cfxPlayerScoreUI = {} -cfxPlayerScoreUI.version = "2.0.0" +cfxPlayerScoreUI.version = "2.0.1" cfxPlayerScoreUI.verbose = false --[[-- VERSION HISTORY - 1.0.2 - initial version - 1.0.3 - module check - 2.0.0 - removed cfxPlayer dependency, handles own commands + - 2.0.1 - late start capability --]]-- -cfxPlayerScoreUI.rootCommands = {} -- by unit's group name, for player aircraft +cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft -- redirect: avoid the debug environ of missionCommand function cfxPlayerScoreUI.redirectCommandX(args) @@ -23,7 +24,7 @@ function cfxPlayerScoreUI.doCommandX(args) local gid = theGroup:getID() if not cfxPlayerScore.scoreTextForPlayerNamed then - trigger.action.outText("***pSGui: CANNOT FIND PlayerScore MODULE", 30) + trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30) return end local desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName) @@ -35,10 +36,7 @@ end -- event handling: we are only interested in birth events -- for player aircraft -- -function cfxPlayerScoreUI:onEvent(event) - if event.id ~= 15 then return end -- only birth - if not event.initiator then return end -- no initiator, no joy - local theUnit = event.initiator +function cfxPlayerScore.processPlayerUnit(theUnit) if not theUnit.getPlayerName then return end -- no player name, bye! local playerName = theUnit:getPlayerName() if not playerName then return end @@ -61,13 +59,7 @@ function cfxPlayerScoreUI:onEvent(event) -- we need to install a group menu item for scores. -- will persist through death local commandTxt = "Show Score / Kills" - local theCommand = missionCommands.addCommandForGroup( - gid, - commandTxt, - nil, -- root level - cfxPlayerScoreUI.redirectCommandX, - {groupName, playerName, "score"} - ) + local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, nil, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"} ) cfxPlayerScoreUI.rootCommands[groupName] = theCommand if cfxPlayerScoreUI.verbose then @@ -75,13 +67,21 @@ function cfxPlayerScoreUI:onEvent(event) end end +function cfxPlayerScoreUI:onEvent(event) + if event.id ~= 15 then return end -- only birth + if not event.initiator then return end -- no initiator, no joy + local theUnit = event.initiator + cfxPlayerScore.processPlayerUnit(theUnit) +end + -- -- Start -- function cfxPlayerScoreUI.start() -- install the event handler for new player planes world.addEventHandler(cfxPlayerScoreUI) - + -- process all existing players (late start) + dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit) trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30) return true end diff --git a/modules/cfxSmokeZones.lua b/modules/cfxSmokeZones.lua index c1d5870..56fffc0 100644 --- a/modules/cfxSmokeZones.lua +++ b/modules/cfxSmokeZones.lua @@ -1,5 +1,5 @@ cfxSmokeZone = {} -cfxSmokeZone.version = "1.1.2" +cfxSmokeZone.version = "1.1.3" cfxSmokeZone.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -18,6 +18,7 @@ cfxSmokeZone.requiredLibs = { 1.1.0 - Watchflag upgrade 1.1.1 - stopSmoke? input 1.1.2 - 'agl', 'alt' synonymous for altitude to keep in line with fireFX + 1.1.3 - corrected smokeTriggerMethod in zone definition --]]-- cfxSmokeZone.smokeZones = {} @@ -67,7 +68,7 @@ function cfxSmokeZone.processSmokeZone(aZone) aZone.smokeTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change") if cfxZones.hasProperty(aZone, "smokeTriggerMethod") then - aZone.delayTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "smokeTriggerMethod", "change") + aZone.smokeTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "smokeTriggerMethod", "change") end end diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index bbc6d1b..7704949 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.7.4" +cfxSpawnZones.version = "1.7.5" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -66,6 +66,8 @@ cfxSpawnZones.spawnedGroups = {} 1.7.2 - baseName now can can be set to zone name by issuing "*" 1.7.3 - ability to hand off to delicates, useDelicates attribute 1.7.4 - wait-attackZone fixes + 1.7.5 - improved verbosity on spawning + - getRequestableSpawnersInRange() ignores height for distance - types - type strings, comma separated @@ -204,6 +206,9 @@ function cfxSpawnZones.createSpawner(inZone) theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false) if theSpawner.requestable then theSpawner.paused = true + if inZone.verbose or cfxSpawnZones.verbose then + trigger.action.outText("+++spwn: spawner <" .. inZone.name .. "> paused: requestable enabled", 30) + end end if cfxZones.hasProperty(inZone, "target") then theSpawner.target = cfxZones.getStringFromZoneProperty(inZone, "target", "") @@ -248,7 +253,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide) for aZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do -- iterate all zones and collect those that match local hasMatch = true - local delta = dcsCommon.dist(aPoint, aZone.point) + local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone)) if delta>aRange then hasMatch = false end if aSide ~= 0 then -- check if side is correct for owned zone @@ -333,9 +338,20 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner) theCoalition, aSpawner.baseName .. "-" .. aSpawner.count, -- must be unique aSpawner.zone, - unitTypes, + unitTypes, aSpawner.formation, aSpawner.heading) + if cfxSpawnZones.verbose or theZone.verbose then + -- check created group size versus requested size + trigger.action.outText("+++spwn: created <" .. theGroup:getSize() .. "> units, requested <" .. #unitTypes .. "> units, formation <" .. aSpawner.formation .. ">", 30) + trigger.action.outText("+++spwn: zone <" .. theZone.name .. ">center at <" .. dcsCommon.point2text(p) .. ">", 30) + local allUnits = theGroup:getUnits() + for idx, myUnit in pairs (allUnits) do + local pos = myUnit:getPoint() + trigger.action.outText("unit <" .. myUnit:getName() .. "> at " .. dcsCommon.point2text(pos), 30) + end + end + aSpawner.theSpawn = theGroup aSpawner.count = aSpawner.count + 1 diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index b6b422d..f7f89de 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "3.0.8" +cfxZones.version = "3.0.9" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -126,6 +126,7 @@ cfxZones.version = "3.0.8" - new createSimpleQuadZone() - 3.0.7 - getPoint() can also get land y when passing true as second param - 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig) +- 3.0.9 - new getFlareColorStringFromZoneProperty() --]]-- cfxZones.verbose = false @@ -2241,6 +2242,30 @@ function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, defa return default end +function cfxZones.getFlareColorStringFromZoneProperty(theZone, theProperty, default) -- smoke as 'red', 'green', or 1..5 + if not default then default = "red" end + local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default) + s = s:lower() + s = dcsCommon.trim(s) + -- check numbers + if (s == "rnd") then return "random" end + if (s == "0") then return "green" end + if (s == "1") then return "red" end + if (s == "2") then return "white" end + if (s == "3") then return "yellow" end + if (s == "-1") then return "random" end + + if s == "green" or + s == "red" or + s == "white" or + s == "yellow" or + s == "random" then + return s end + + return default +end + + -- -- Zone-based wildcard processing -- diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index abfbc48..cfd0480 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -1,5 +1,5 @@ csarManager = {} -csarManager.version = "2.2.5" +csarManager.version = "2.2.6" csarManager.verbose = false csarManager.ups = 1 @@ -63,6 +63,8 @@ csarManager.ups = 1 - 2.2.4 - CSAR attribute value defaults name - start? attribute for CSAR as startCSAR? synonym - 2.2.5 - manual freq for CSAR was off by a factor of 10 - Corrected + - 2.2.6 - useFlare, now also launches a flare in addition to smoke + - zone testing uses getPoint for zones, supports moving csar zones INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED @@ -1028,6 +1030,18 @@ function csarManager.updateCSARMissions() csarManager.openMissions = newMissions -- this is the new batch end +function csarManager.launchFlare(args) + local color = args.color + if color < 0 then color = math.random(4) - 1 end + local loc = args.loc -- with height + if csarManager.verbose then + trigger.action.outText("+++csarM: launching flare, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. ")", 30) + end + trigger.action.outTextForGroup(args.uID, "Launching flare!", 30) + loc.y = loc.y + 3 -- launch 3 meters above ground + trigger.action.signalFlare(loc, color, 0) +end + function csarManager.update() -- every second -- schedule next invocation timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups) @@ -1053,7 +1067,8 @@ function csarManager.update() -- every second -- enough to trigger comms for idx, csarMission in pairs (csarManager.openMissions) do -- check if we are inside trigger range on the same side - local d = dcsCommon.distFlat(uPoint, csarMission.zone.point) + local mp = cfxZones.getPoint(csarMission.zone, true) + local d = dcsCommon.distFlat(uPoint, mp) if ((uSide == csarMission.side) or (csarMission.side == 0) ) and (d < csarManager.rescueTriggerRange) then -- we are in trigger distance. if we did not notify before @@ -1065,7 +1080,21 @@ function csarManager.update() -- every second local oclock = dcsCommon.clockPositionOfARelativeToB(csarMission.zone.point, uPoint, ownHeading) .. " o'clock" local msg = "\n" .. uName ..", " .. csarMission.name .. ". We can hear you, check your " .. oclock if csarManager.useSmoke then msg = msg .. " - popping smoke" end + if csarManager.useFlare then + if csarManager.useSmoke then + msg = msg .. " and will launch flare in a few seconds" + else + msg = msg .. " - preparing flare" + end + -- schedule flare launch in 5-10 seconds + local args = {} + args.loc = mp + args.color = csarManager.flareColor + args.uID = uID + timer.scheduleFunction(csarManager.launchFlare, args, timer.getTime() + math.random(5)) + end msg = msg .. "." + if csarMission.isHot then msg = msg .. " Be advised: LZ is hot." end @@ -1394,6 +1423,10 @@ function csarManager.readConfigZone() csarManager.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "blue") csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor) + csarManager.useFlare = cfxZones.getBoolFromZoneProperty(theZone, "useFlare", true) + csarManager.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flareColor", "red") + csarManager.flareColor = dcsCommon.flareColor2Num(csarManager.flareColor) + if cfxZones.hasProperty(theZone, "csarRedDelivered!") then csarManager.csarRedDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarRedDelivered!", "*") diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 8176845..f6c7ad1 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.8.5" +dcsCommon.version = "2.8.7" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -147,7 +147,9 @@ dcsCommon.version = "2.8.5" 2.8.5 - better guard in getGroupUnit() 2.8.6 - phonetic helpers new spellString() - + 2.8.7 - new flareColor2Num() + - new flareColor2Text() + - new iteratePlayers() --]]-- @@ -2545,6 +2547,15 @@ end return ("unknown: " .. smokeColor) end + function dcsCommon.flareColor2Text(flareColor) + if (flareColor == 0) then return "Green" end + if (flareColor == 1) then return "Red" end + if (flareColor == 2) then return "White" end + if (flareColor == 3) then return "Yellow" end + if (flareColor < 0) then return "Random" end + return ("unknown: " .. flareColor) + end + function dcsCommon.smokeColor2Num(smokeColor) if not smokeColor then smokeColor = "green" end if type(smokeColor) ~= "string" then return 0 end @@ -2556,6 +2567,20 @@ end if (smokeColor == "blue") then return 4 end return 0 end + + function dcsCommon.flareColor2Num(flareColor) + if not flareColor then flareColor = "green" end + if type(flareColor) ~= "string" then return 0 end + flareColor = flareColor:lower() + if (flareColor == "green") then return 0 end + if (flareColor == "red") then return 1 end + if (flareColor == "white") then return 2 end + if (flareColor == "yellow") then return 3 end + if (flareColor == "random") then return -1 end + if (flareColor == "rnd") then return -1 end + return 0 + end + function dcsCommon.markPointWithSmoke(p, smokeColor) if not smokeColor then smokeColor = 0 end @@ -3315,6 +3340,23 @@ function dcsCommon.spellString(inString) return res end +-- +-- iterators +-- +-- iteratePlayers - call callback for all player units +-- callback is of signature callback(playerUnit) +-- + +function dcsCommon.iteratePlayers(callBack) + local factions = {0, 1, 2} + for idx, theFaction in pairs(factions) do + local players = coalition.getPlayers(theFaction) + for idy, theUnit in pairs(players) do + callBack(theUnit) + end + end +end + -- -- SEMAPHORES -- diff --git a/modules/flareZone.lua b/modules/flareZone.lua new file mode 100644 index 0000000..a1a6979 --- /dev/null +++ b/modules/flareZone.lua @@ -0,0 +1,116 @@ +flareZone = {} +flareZone.version = "1.0.0" +flareZone.verbose = false +flareZone.name = "flareZone" + +--[[-- VERSION HISTORY + 1.0.0 - initial version +--]]-- +flareZone.requiredLibs = { + "dcsCommon", + "cfxZones", +} +flareZone.flares = {} -- all flare zones + +function flareZone.addFlareZone(theZone) + theZone.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flare", "green") + theZone.flareColor = dcsCommon.flareColor2Num(theZone.flareColor) + if cfxZones.hasProperty(theZone, "f?") then + cfxZones.theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "f?", "") + elseif cfxZones.hasProperty(theZone, "launchFlare?") then + theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "launchFlare?", "") + else + theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "launch?", "") + end + theZone.lastDoFlare = trigger.misc.getUserFlag(theZone.doFlare) + -- triggerMethod + theZone.flareTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + if cfxZones.hasProperty(theZone, "flareTriggerMethod") then + theZone.flareTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "flareTriggerMethod", "change") + end + + theZone.azimuthL, theZone.azimuthH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "direction", 90) -- in degrees + -- in DCS documentation, the parameter is incorrectly called 'azimuth' + if cfxZones.hasProperty(theZone, "azimuth") then + theZone.azimuthL, theZone.azimuthH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "azimuth", 90) -- in degrees + end +-- theZone.azimuth = theZone.azimuth * 0.0174533 -- rads + theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "altitude", 1) + if cfxZones.hasProperty(theZone, "alt") then + theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "alt", 1) + elseif cfxZones.hasProperty(theZone, "flareAlt") then + theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "flareAlt", 1) + elseif cfxZones.hasProperty(theZone, "agl") then + theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "agl", 1) + end + + theZone.salvoSizeL, theZone.salvoSizeH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "salvo", 1) + + theZone.salvoDurationL, theZone.salvoDurationH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "duration", 1) + + if theZone.verbose or flareZone.verbose then + trigger.action.outText("+++flrZ: new flare <" .. theZone.name .. ">, color (" .. theZone.flareColor .. ")", 30) + end + table.insert(flareZone.flares, theZone) +end + +function flareZone.launch(theZone) + local color = theZone.flareColor + if color < 0 then color = math.random(4) - 1 end + if flareZone.verbose or theZone.verbose then + trigger.action.outText("+++flrZ: launching <" .. theZone.name .. ">, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. ")", 30) + end + local loc = cfxZones.getPoint(theZone, true) -- with height + loc.y = loc.y + theZone.flareAlt + -- calculate azimuth + local azimuth = cfxZones.randomInRange(theZone.azimuthL, theZone.azimuthH) * 0.0174533 -- in rads + trigger.action.signalFlare(loc, color, azimuth) +end + +function flareZone.update() + -- call me again in a second + timer.scheduleFunction(flareZone.update, {}, timer.getTime() + 1) + + -- launch if flag banged + for idx, theZone in pairs(flareZone.flares) do + if cfxZones.testZoneFlag(theZone, theZone.doFlare, theZone.flareTriggerMethod, "lastDoFlare") then + local salvo = cfxZones.randomInRange(theZone.salvoSizeL, theZone.salvoSizeH) + if salvo < 2 then + -- one-shot + flareZone.launch(theZone) + else + -- pick a duration from range + local duration = cfxZones.randomInRange(theZone.salvoDurationL, theZone.salvoDurationH) + local duration = duration / salvo + local d = 0 + for l=1, salvo do + timer.scheduleFunction(flareZone.launch, theZone, timer.getTime() + d + 0.1) + d = d + duration + end + end + end + end +end + +function flareZone.start() + if not dcsCommon.libCheck("cfx Flare Zones", flareZone.requiredLibs) then return false end + + -- collect all flares + local attrZones = cfxZones.getZonesWithAttributeNamed("flare") + for k, theZone in pairs(attrZones) do + flareZone.addFlareZone(theZone) -- process attribute and add to zone + end + + -- start update + flareZone.update() -- also starts all unpaused + + -- say hi + trigger.action.outText("cfx Flare Zone v" .. flareZone.version .. " started.", 30) + return true +end + +-- let's go +if not flareZone.start() then + trigger.action.outText("cf/x Flare Zones aborted: missing libraries", 30) + cfxSmokeZone = nil +end \ No newline at end of file diff --git a/modules/playerZone.lua b/modules/playerZone.lua new file mode 100644 index 0000000..98703e1 --- /dev/null +++ b/modules/playerZone.lua @@ -0,0 +1,158 @@ +playerZone = {} +playerZone.version = "1.0.0" +playerZone.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +playerZone.playerZones = {} +--[[-- + Version History + 1.0.0 - Initial version + +--]]-- + +function playerZone.createPlayerZone(theZone) + -- start val - a range + theZone.pzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "playerZone", 0) + + + -- Method for outputs + theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") + if cfxZones.hasProperty(theZone, "pzMethod") then + theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc") + end + + if cfxZones.hasProperty(theZone, "pNum") then + theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum", "none") + end + + if cfxZones.hasProperty(theZone, "added!") then + theZone.pAdd = cfxZones.getStringFromZoneProperty(theZone, "added!", "none") + end + + if cfxZones.hasProperty(theZone, "gone!") then + theZone.pRemove = cfxZones.getStringFromZoneProperty(theZone, "gone!", "none") + end + + theZone.playersInZone = {} -- indexed by unit name +end + + +function playerZone.collectPlayersForZone(theZone) + local factions = {0, 1, 2} + local zonePlayers = {} + for idx, f in pairs (factions) do + if theZone.pzCoalition == 0 or f == theZone.pzCoalition then + local allPlayers = coalition.getPlayers(f) + for idy, theUnit in pairs (allPlayers) do + local loc = theUnit:getPoint() + if cfxZones.pointInZone(loc, theZone) then + zonePlayers[theUnit:getName()] = theUnit + end + end + end + end + return zonePlayers +end + +function playerZone.processZone(theZone) + local nowInZone = playerZone.collectPlayersForZone(theZone) + -- find new players in zone + local hasNew = false + local newCount = 0 + for name, theUnit in pairs(nowInZone) do + if not theZone.playersInZone[name] then + -- this unit was not here last time + hasNew = true + if playerZone.verbose or theZone.verbose then + trigger.action.outText("+++pZone: new player unit <" .. name .. "> in zone <" .. theZone.name .. ">", 30) + end + end + newCount = newCount + 1 + end + -- find if players have left the zone + local hasGone = false + for name, theUnit in pairs(theZone.playersInZone) do + if not nowInZone[name] then + hasGone = true + if playerZone.verbose or theZone.verbose then + trigger.action.outText("+++pZone: player unit <" .. name .. "> disappeared from <" .. theZone.name .. ">", 30) + end + end + end + + -- flag handling and banging + if theZone.pNum then + cfxZones.setFlagValueMult(theZone.pNum, newCount, theZone) + end + + if theZone.pAdd and hasNew then + if theZone.verbose or playerZone.verbose then + trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'added!' flags <" .. theZone.pAdd .. ">", 30) + end + cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone) + end + + if theZone.pRemove and hasGone then + if theZone.verbose or playerZone.verbose then + trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'gone' flags <" .. theZone.pRemove .. ">", 30) + end + cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone) + end +end + +-- +-- Update +-- +function playerZone.update() + -- re-invoke in 1 second + timer.scheduleFunction(playerZone.update, {}, timer.getTime() + 1) + + -- iterate all zones and check them + for idx, theZone in pairs(playerZone.playerZones) do + playerZone.processZone(theZone) + end +end + +-- +-- Read Config Zone +-- +function playerZone.readConfigZone(theZone) + -- currently nothing to do +end + +-- +-- Start +-- +function playerZone.start() + if not dcsCommon.libCheck("cfx Player Zone", + playerZone.requiredLibs) + then return false end + + local theZone = cfxZones.getZoneByName("playerZoneConfig") + if not theZone then + theZone = cfxZones.createSimpleZone("playerZoneConfig") + end + playerZone.readConfigZone(theZone) + + local pZones = cfxZones.zonesWithProperty("playerZone") + for k, aZone in pairs(pZones) do + playerZone.createPlayerZone(aZone) + playerZone.playerZones[aZone.name] = aZone + end + + -- start update cycle + playerZone.update() + return true +end + +if not playerZone.start() then + trigger.action.outText("+++ aborted playerZone v" .. playerZone.version .. " -- start failed", 30) + playerZone = nil +end + +--[[-- + additional features: + - filter by type + - filter by cat +--]]-- \ No newline at end of file diff --git a/modules/shallows.lua b/modules/shallows.lua new file mode 100644 index 0000000..136c9d2 --- /dev/null +++ b/modules/shallows.lua @@ -0,0 +1,61 @@ +shallows = {} +-- script to remove dead naval hulls that failed to sink +-- once dead, smoke is put over the hull for 5 minutes +-- and if still in game later, the hull is removed + +shallows.version = "1.0.0" +shallows.removeAfter = 5 -- minutes after kill event +shallows.verbose = false +shallows.uuid = 1 + +-- uuid +function shallows.getUuid() + shallows.uuid = shallows.uuid + 1 + return shallows.uuid +end + +-- remove hull +function shallows.removeHull(args) + if shallows.verbose then + trigger.action.outText("enter remove hull for <" .. args.name .. ">", 30) + end + + -- remove smoke and whatever's left of ship + trigger.action.effectSmokeStop(args.sName) + Object.destroy(args.theUnit) + if shallows.verbose then + trigger.action.outText("Shallows: Removed <" .. args.name .. ">", 30) + end +end + +-- watch the world turn and ships get killed +function shallows:onEvent(event) + if event.id ~= 28 then return end -- only kill events + if not event.target then return end + -- must be a ship + local theUnit = event.target + if not theUnit.getGroup or not theUnit:getGroup() then return end + local theGroup = theUnit:getGroup() + local cat = theGroup:getCategory() + if cat ~= 3 then return end -- not a ship + + if shallows.verbose then + trigger.action.outText("Shallows: marking <" .. theUnit:getName() .. "> for deep-sixing", 30) + end + + -- mark it with smoke and fire + local pos = theUnit:getPoint() + local sName = theUnit:getName() .. shallows.getUuid() + trigger.action.effectSmokeBig(pos, 2, 0.5, sName) + + -- set timer to re-visit later + local args = {} + args.name = theUnit:getName() + args.sName = sName + args.theUnit = theUnit + timer.scheduleFunction(shallows.removeHull, args, timer.getTime() + shallows.removeAfter * 60) +end + +-- start +world.addEventHandler(shallows) +trigger.action.outText("shallows " .. shallows.version .. " started", 30) \ No newline at end of file diff --git a/modules/williePete.lua b/modules/williePete.lua index 59167de..8f590bc 100644 --- a/modules/williePete.lua +++ b/modules/williePete.lua @@ -1,5 +1,5 @@ williePete = {} -williePete.version = "1.0.1" +williePete.version = "1.0.2" williePete.ups = 10 -- we update at 10 fps, so accuracy of a -- missile moving at Mach 2 is within 33 meters, -- with interpolation even at 3 meters @@ -12,7 +12,8 @@ williePete.requiredLibs = { --[[-- Version History 1.0.0 - Initial version - 1.0.1 - update to suppress verbosity + 1.0.1 - update to suppress verbosity + 1.0.2 - added Gazelle WP --]]-- @@ -23,7 +24,7 @@ williePete.blastedObjects = {} -- used when we detonate something -- recognizes WP munitions. May require regular update when new -- models come out. -williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM"} +williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM", "SNEB_TYPE254_H1_GREEN", "SNEB_TYPE254_H1_RED", "SNEB_TYPE254_H1_YELLOW"} function williePete.addWillie(theWillie) table.insert(williePete.willies, theWillie) diff --git a/tutorial & demo missions/demo - Effects with a Flare.miz b/tutorial & demo missions/demo - Effects with a Flare.miz new file mode 100644 index 0000000..0805d0b Binary files /dev/null and b/tutorial & demo missions/demo - Effects with a Flare.miz differ diff --git a/tutorial & demo missions/demo - Not too shallow at all.miz b/tutorial & demo missions/demo - Not too shallow at all.miz new file mode 100644 index 0000000..01bcd5b Binary files /dev/null and b/tutorial & demo missions/demo - Not too shallow at all.miz differ diff --git a/tutorial & demo missions/demo - Players in the Zone.miz b/tutorial & demo missions/demo - Players in the Zone.miz new file mode 100644 index 0000000..b076c93 Binary files /dev/null and b/tutorial & demo missions/demo - Players in the Zone.miz differ diff --git a/tutorial & demo missions/demo - feats and autoCSAR.miz b/tutorial & demo missions/demo - feats and autoCSAR.miz index b839730..9653730 100644 Binary files a/tutorial & demo missions/demo - feats and autoCSAR.miz and b/tutorial & demo missions/demo - feats and autoCSAR.miz differ