diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 53185fb..22f110d 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 b2db7d4..e444d35 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxOwnedZones.lua b/modules/cfxOwnedZones.lua index f378a71..247cfcd 100644 --- a/modules/cfxOwnedZones.lua +++ b/modules/cfxOwnedZones.lua @@ -1,5 +1,5 @@ cfxOwnedZones = {} -cfxOwnedZones.version = "2.0.0" +cfxOwnedZones.version = "2.0.1" cfxOwnedZones.verbose = false cfxOwnedZones.announcer = true cfxOwnedZones.name = "cfxOwnedZones" @@ -21,6 +21,7 @@ cfxOwnedZones.name = "cfxOwnedZones" - heloCap option - fixWingCap option - filter water owned zones for groundTroops +2.0.1 - RGBA colors can be entered hex style #ff340799 --]]-- cfxOwnedZones.requiredLibs = { @@ -838,8 +839,6 @@ end masterOwner input for zones, overrides all else when not neutral dont count zones that cant be conquered for allBlue/allRed - - define color with #FF008080 - + --]]-- diff --git a/modules/cfxPlayerScore.lua b/modules/cfxPlayerScore.lua index e691ae9..dd1499b 100644 --- a/modules/cfxPlayerScore.lua +++ b/modules/cfxPlayerScore.lua @@ -1,5 +1,5 @@ cfxPlayerScore = {} -cfxPlayerScore.version = "2.0.1" +cfxPlayerScore.version = "2.1.1" cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav" @@ -72,6 +72,14 @@ cfxPlayerScore.firstSave = true -- to force overwrite - immediate awarding of all negative scores, even if deferred 2.0.1 - corrected access to nowString() - more robust config reading + 2.1.0 - coalition score + - reportCoalition switch + - persist coalition score + - add score to coalition when scoring player + 2.1.1 - check ownership of scoreSafe zone upon touch-down + - new scoreSummaryForPlayersOfCoalition() + - new noGrief option in config + - improved guards when checking ownership (nil zone owner) --]]-- @@ -79,14 +87,16 @@ cfxPlayerScore.requiredLibs = { "dcsCommon", -- this is doing score keeping "cfxZones", -- zones for config } -cfxPlayerScore.playerScore = {} -- init to empty +cfxPlayerScore.playerScore = {} -- indexed by playerName +cfxPlayerScore.coalitionScore = {} -- score per coalition +cfxPlayerScore.coalitionScore[1] = 0 -- init red +cfxPlayerScore.coalitionScore[2] = 0 -- init blue cfxPlayerScore.deferred = false -- on deferred, we only award after landing, and erase on any form of re-slot cfxPlayerScore.delayAfterLanding = 10 -- seconds after landing cfxPlayerScore.safeZones = {} -- safe zones to land in cfxPlayerScore.featZones = {} -- zones that define feats cfxPlayerScore.killZones = {} -- when set, kills only count here - -- typeScore: dictionary sorted by typeString for score -- extend to add more types. It is used by unitType2score to -- determine the base unit score @@ -107,7 +117,6 @@ cfxPlayerScore.train = 5 cfxPlayerScore.landing = 0 -- if > 0 it scores as feat cfxPlayerScore.unit2player = {} -- lookup and reverse look-up ---cfxPlayerScore.player2unit = {} -- to detect death and destruction function cfxPlayerScore.addSafeZone(theZone) theZone.scoreSafe = cfxZones.getCoalitionFromZoneProperty(theZone, "scoreSafe", 0) @@ -154,29 +163,25 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi -- and location of victim for kill -- coa is coalition of landing unit -- and coalition of killer for kill --- trigger.action.outText("enter feat check for <" .. featType .. ">", 30) + if not coa then coa = 0 end if not featType then featType = "KILL" end featType = string.upper(featType) local theFeats = {} for idx, theZone in pairs(cfxPlayerScore.featZones) do --- trigger.action.outText("featcheck: <" .. theZone.name .. ">", 30) local canAward = true -- check if it can be awarded if theZone.featNum == 0 then canAward = false --- trigger.action.outText(" - failed featNum", 30) end if theZone.featType ~= featType then canAward = false - --trigger.action.outText(" - failed type check (look for <" .. featType .. ">, got <" .. theZone.featType .. ">", 30) end if not (theZone.coalition == 0 or theZone.coalition == coa) then canAward = false - -- trigger.action.outText(" - failed coa check ", 30) end if featType == "PVP" then @@ -191,21 +196,17 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi if not cfxZones.pointInZone(loc, theZone) then canAward = false - -- trigger.action.outText(" - failed loc check ", 30) end if theZone.ppOnce then if theZone.awardedTo[name] then canAward = false - --trigger.action.outText(" - already awarded fail ", 30) end end if canAward then table.insert(theFeats, theZone) -- jupp, add it - --trigger.action.outText(" can award", 30) else - --trigger.action.outText("FAIL.", 30) end end @@ -240,7 +241,6 @@ function cfxPlayerScore.preprocessWildcards(inMsg, aUnit, aVictim) theMsg = theMsg:gsub("", aVictim:getTypeName()) -- victim may not have group. guard against that -- happens if unit 'cooks off' - --local gName = "(unknown)" local aGroup = nil if aVictim.getGroup then aVictim:getGroup() @@ -290,9 +290,8 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group inName = tostring(inName) end - -- now, since 2.7x DCS turns units into static objects for + -- since 2.7x DCS turns units into static objects for -- cooking off, so first thing we need to do is do a name check - local objectScore = cfxPlayerScore.typeScore[inName] if not objectScore then -- try the type desc @@ -384,6 +383,20 @@ function cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score) local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName) thePlayerScore.score = thePlayerScore.score + score cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) + -- if coalitionScore is active, trace player back to their current + -- coalition and add points to that coalition if positive + -- or always if noGrief is true + local pFaction = dcsCommon.playerName2Coalition(playerName) + if cfxPlayerScore.noGrief then + -- only on positive score + if (score > 0) and pFaction > 0 then + cfxPlayerScore.coalitionScore[pFaction] = cfxPlayerScore.coalitionScore[pFaction] + score + end + else + if pFaction > 0 then + cfxPlayerScore.coalitionScore[pFaction] = cfxPlayerScore.coalitionScore[pFaction] + score + end + end return thePlayerScore.score end @@ -395,10 +408,6 @@ function cfxPlayerScore.updateScoreForPlayer(playerName, score) cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write-through. why? because it may be a new entry. return thePlayerScore.score -- this is the old score!!! end - --local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName) - --thePlayerScore.score = thePlayerScore.score + score - --cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) - --return thePlayerScore.score -- now write immediately return cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score) end @@ -524,13 +533,34 @@ function cfxPlayerScore.scoreTextForPlayerNamed(playerName) return cfxPlayerScore.playerScore2text(thePlayerScore) end +function cfxPlayerScore.scoreSummaryForPlayersOfCoalition(side) + -- only list players who are in the coalition RIGHT NOW + -- only list their score + if not side then side = -1 end + local desc = "\nCurrent score for players in " .. dcsCommon.coalition2Text(side) .." coalition:\n" + local count = 0 + for pName, pScore in pairs(cfxPlayerScore.playerScore) do + local coa = dcsCommon.playerName2Coalition(pName) + if coa == side then + desc = desc .. pName ..": " .. pScore.score .. "\n" + count = count + 1 + end + end + if count < 1 then + desc = desc .. " (No score yet)" + end + + desc = desc .. "\n" + return desc +end + function cfxPlayerScore.scoreTextForAllPlayers(ranked) if not ranked then ranked = false end local theText = "" local isFirst = true local theScores = cfxPlayerScore.playerScore if cfxPlayerScore.verbose then - trigger.action.outText("+++pScr: Saving score - <" .. dcsCommon.getSizeOfTable(theScores) .. "> entries.", 30) + trigger.action.outText("+++pScr: Generating score - <" .. dcsCommon.getSizeOfTable(theScores) .. "> entries.", 30) end if ranked then table.sort(theScores, function(left, right) return left.score < right.score end ) @@ -548,6 +578,17 @@ function cfxPlayerScore.scoreTextForAllPlayers(ranked) isFirst = false rank = rank + 1 end + + if dcsCommon.getSizeOfTable(theScores) < 1 then + theText = theText .. " (No score yet)\n" + end + + if cfxPlayerScore.reportCoalition then + --theText = theText .. "\n" + theText = theText .. "\nRED total: " .. cfxPlayerScore.coalitionScore[1] + theText = theText .. "\nBLUE total: " .. cfxPlayerScore.coalitionScore[2] + end + return theText end @@ -585,6 +626,10 @@ function cfxPlayerScore.awardScoreTo(killSide, theScore, killerName) trigger.action.outTextForCoalition(killSide, "Killscore: " .. theScore .. ", now " .. thePlayerRecord.scoreaccu .. " waiting for " .. killerName .. ", awarded after landing", 30) else -- negative score or not deferred trigger.action.outTextForCoalition(killSide, "Killscore: " .. theScore .. " for a total of " .. playerScore .. " for " .. killerName, 30) + + if cfxPlayerScore.reportCoalition then + trigger.action.outTextForCoalition(killSide, "\nCoalition Total: " .. cfxPlayerScore.coalitionScore[killSide], 30) + end end end end @@ -937,20 +982,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent) -- only continue if there is anything to award local killSize = dcsCommon.getSizeOfTable(theScore.killQueue) local featSize = dcsCommon.getSizeOfTable(theScore.featQueue) - --trigger.action.outText("+++pScr: kS = <" .. killSize .. ">, fS = <" .. featSize .. ">, Accu = <" .. theScore.scoreaccu .. ">", 30) - - -- to avoid possible race conditions with other modules that - -- trigger on landing, we always schedule the check in 10 seconds - --[[-- - if killSize < 1 and - featSize < 1 and - theScore.scoreaccu < 1 then - if cfxPlayerScore.verbose then - trigger.action.outText("+++pScr: deferred and nothing to award after touchdown to <" .. playerName .. ">, returning", 30) - end - return - end - --]]-- if cfxPlayerScore.verbose then trigger.action.outText("+++pScr: prepping deferred score for <" .. playerName ..">", 30) @@ -964,8 +995,16 @@ function cfxPlayerScore.handlePlayerLanding(theEvent) local isSafe = false for idx, theZone in pairs(cfxPlayerScore.safeZones) do if theZone.scoreSafe == 0 or theZone.scoreSafe == coa then - if cfxZones.pointInZone(loc, theZone) then - isSafe = true + -- make sure that this zone doesn't belong to the + -- wrong faction (if owned zone) + if (theZone.owner == coa) or (theZone.owner == 0) or (theZone.owner == nil) then + if cfxZones.pointInZone(loc, theZone) then + isSafe = true + end + else + if cfxPlayerScore.verbose then + trigger.action.outText("+++pSc: Zone <" .. theZone.name .. ">: owner=<" .. theZone.owner .. ">, my coa=<" .. coa .. ">, no owner match") + end end end end @@ -1014,7 +1053,8 @@ function cfxPlayerScore.scheduledAward(args) local coa = theUnit:getCoalition() local isSafe = false for idx, theZone in pairs(cfxPlayerScore.safeZones) do - if theZone.scoreSafe == 0 or theZone.scoreSafe == coa then + if theZone.scoreSafe == 0 or theZone.scoreSafe == coa then + -- we no longer check ownership of zone, we did that when we landed if cfxZones.pointInZone(loc, theZone) then isSafe = true end @@ -1028,6 +1068,11 @@ function cfxPlayerScore.scheduledAward(args) local theScore = cfxPlayerScore.getPlayerScore(playerName) + local playerSide = dcsCommon.playerName2Coalition(playerName) + if playerSide < 1 then + trigger.action.outText("+++pScr: WARNING - unaffiliated player <" .. playerName .. ">, score award ignored", 30) + return + end if dcsCommon.getSizeOfTable(theScore.killQueue) < 1 and dcsCommon.getSizeOfTable(theScore.featQueue) < 1 and theScore.scoreaccu < 1 then @@ -1042,9 +1087,10 @@ function cfxPlayerScore.scheduledAward(args) -- when we get here we award all scores, kills, and feats local desc = "\nPlayer " .. playerName .. " is awarded:\n" -- score and total score - if theScore.scoreaccu > 0 then + if theScore.scoreaccu > 0 then -- remember: negatives are immediate theScore.score = theScore.score + theScore.scoreaccu desc = desc .. " score: " .. theScore.scoreaccu .. " for a new total of " .. theScore.score .. "\n" + cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu theScore.scoreaccu = 0 hasAward = true end @@ -1074,6 +1120,10 @@ function cfxPlayerScore.scheduledAward(args) end theScore.featQueue = {} + if cfxPlayerScore.reportCoalition then + desc = desc .. "\nCoalition Total: " .. cfxPlayerScore.coalitionScore[playerSide] + end + -- output score desc = desc .. "\n" if hasAward then @@ -1129,7 +1179,8 @@ function cfxPlayerScore.handlePlayerEvent(theEvent) local playerName = thePlayerUnit:getPlayerName() local theScore = cfxPlayerScore.getPlayerScore(playerName) -- now re-init feat and score queues - if theScore.scoreaccu > 0 then + + if theScore.scoreaccu and theScore.scoreaccu > 0 then trigger.action.outTextForCoalition(playerSide, "Player " .. playerName .. ", score of <" .. theScore.scoreaccu .. "> points discarded.", 30) end theScore.scoreaccu = 0 @@ -1209,6 +1260,11 @@ function cfxPlayerScore.readConfigZone(theZone) cfxPlayerScore.reportScore = cfxZones.getBoolFromZoneProperty(theZone, "reportScore", true) cfxPlayerScore.reportFeats = cfxZones.getBoolFromZoneProperty(theZone, "reportFeats", true) + + cfxPlayerScore.reportCoalition = cfxZones.getBoolFromZoneProperty( + theZone, "reportCoalition", false) -- also show coalition score + + cfxPlayerScore.noGrief = cfxZones.getBoolFromZoneProperty(theZone, "noGrief", true) -- noGrief = only add positive score end -- @@ -1220,6 +1276,7 @@ function cfxPlayerScore.saveData() local theScore = dcsCommon.clone(cfxPlayerScore.playerScore) theData.theScore = theScore -- build feat zone list + theData.coalitionScore = dcsCommon.clone(cfxPlayerScore.coalitionScore) local featZones = {} for idx, theZone in pairs(cfxPlayerScore.featZones) do local theFeat = {} @@ -1243,6 +1300,9 @@ function cfxPlayerScore.loadData() local theScore = theData.theScore cfxPlayerScore.playerScore = theScore + if theData.coalitionScore then + cfxPlayerScore.coalitionScore = theData.coalitionScore + end local featData = theData.featData if featData then for name, data in pairs(featData) do diff --git a/modules/cfxPlayerScoreUI.lua b/modules/cfxPlayerScoreUI.lua index 47831f0..291ee07 100644 --- a/modules/cfxPlayerScoreUI.lua +++ b/modules/cfxPlayerScoreUI.lua @@ -1,5 +1,5 @@ cfxPlayerScoreUI = {} -cfxPlayerScoreUI.version = "2.0.1" +cfxPlayerScoreUI.version = "2.1.0" cfxPlayerScoreUI.verbose = false --[[-- VERSION HISTORY @@ -7,11 +7,22 @@ cfxPlayerScoreUI.verbose = false - 1.0.3 - module check - 2.0.0 - removed cfxPlayer dependency, handles own commands - 2.0.1 - late start capability + - 2.1.0 - soundfile cleanup + - score summary for side + - allowAll + --]]-- - +cfxPlayerScoreUI.requiredLibs = { + "dcsCommon", -- this is doing score keeping + "cfxZones", -- zones for config + "cfxPlayerScore", +} +cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav" cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft +cfxPlayerScoreUI.allowAll = true +cfxPlayerScoreUI.ranked = true --- redirect: avoid the debug environ of missionCommand +-- redirect: avoid the debug environ of missionCommands function cfxPlayerScoreUI.redirectCommandX(args) timer.scheduleFunction(cfxPlayerScoreUI.doCommandX, args, timer.getTime() + 0.1) end @@ -22,14 +33,24 @@ function cfxPlayerScoreUI.doCommandX(args) local what = args[3] -- "score" or other commands local theGroup = Group.getByName(groupName) local gid = theGroup:getID() + local coa = theGroup:getCoalition() if not cfxPlayerScore.scoreTextForPlayerNamed then trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30) return end - local desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName) + local desc = "" + if what == "score" then + desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName) + elseif what == "allMySide" then + desc = cfxPlayerScore.scoreSummaryForPlayersOfCoalition(coa) + elseif what == "all" then + desc = "Score Table For All Players:\n" .. cfxPlayerScore.scoreTextForAllPlayers(cfxPlayerScoreUI.ranked) + else + desc = "PlayerScore UI: unknown command <" .. what .. ">" + end trigger.action.outTextForGroup(gid, desc, 30) - trigger.action.outSoundForGroup(gid, "Quest Snare 3.wav") + trigger.action.outSoundForGroup(gid, cfxPlayerScoreUI.soundFile) end -- @@ -59,8 +80,18 @@ function cfxPlayerScore.processPlayerUnit(theUnit) -- 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, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"} ) - cfxPlayerScoreUI.rootCommands[groupName] = theCommand + local theMenu = missionCommands.addSubMenuForGroup(gid, "Show Score", nil) + local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"}) + + commandTxt = "Show my Side Score / Kills" + theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "allMySide"}) + + if cfxPlayerScoreUI.allowAll then + commandTxt = "Show All Player Scores" + theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "all"}) + end + + cfxPlayerScoreUI.rootCommands[groupName] = theMenu if cfxPlayerScoreUI.verbose then trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30) @@ -78,6 +109,9 @@ end -- Start -- function cfxPlayerScoreUI.start() + if not dcsCommon.libCheck("cfx Player Score UI", + cfxPlayerScoreUI.requiredLibs) + then return false end -- install the event handler for new player planes world.addEventHandler(cfxPlayerScoreUI) -- process all existing players (late start) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 492d632..857b25b 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "3.1.0" +cfxZones.version = "3.1.1" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -129,6 +129,8 @@ cfxZones.version = "3.1.0" - 3.0.9 - new getFlareColorStringFromZoneProperty() - 3.1.0 - new getRGBVectorFromZoneProperty() new getRGBAVectorFromZoneProperty() +- 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format + - owner for all, default 0 --]]-- cfxZones.verbose = false @@ -2245,6 +2247,13 @@ function cfxZones.getRGBAVectorFromZoneProperty(theZone, theProperty, defaultVal if not defaultVal then defaultVal = {1.0, 1.0, 1.0, 1.0} end if #defaultVal ~=4 then defaultVal = {1.0, 1.0, 1.0, 1.0} end local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, "") + s = dcsCommon.trim(s) + if s:sub(1,1) == "#" then + -- it's probably a "#RRGGBBAA" format hex string + local hVec = dcsCommon.hexString2RGBA(s) + if hVec then return hVec end + end + local sVec = dcsCommon.splitString(s, ",") local nVec = {} for i = 1, 4 do @@ -2841,8 +2850,10 @@ function cfxZones.init() cfxZones.readFromDCS(true) -- true: erase old -- pre-read zone owner for all zones - local pZones = cfxZones.zonesWithProperty("owner") - for n, aZone in pairs(pZones) do + -- much like verbose, all zones have owner +-- local pZones = cfxZones.zonesWithProperty("owner") +-- for n, aZone in pairs(pZones) do + for n, aZone in pairs(cfxZones.zones) do aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) end diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index f6c7ad1..54a8f15 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.8.7" +dcsCommon.version = "2.8.8" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -150,7 +150,9 @@ dcsCommon.version = "2.8.7" 2.8.7 - new flareColor2Num() - new flareColor2Text() - new iteratePlayers() - + 2.8.8 - new hexString2RGBA() + - new playerName2Coalition() + - new coalition2Text() --]]-- -- dcsCommon is a library of common lua functions @@ -2955,6 +2957,14 @@ function dcsCommon.coalition2county(inCoalition) end +function dcsCommon.coalition2Text(coa) + if not coa then return "!nil!" end + if coa == 0 then return "NEUTRAL" end + if coa == 1 then return "RED" end + if coa == 2 then return "BLUE" end + return "?UNKNOWN?" +end + function dcsCommon.latLon2Text(lat, lon) -- inspired by mist, thanks Grimes! -- returns two strings: lat and lon @@ -3340,6 +3350,47 @@ function dcsCommon.spellString(inString) return res end +-- +-- RGBA from hex +-- +function dcsCommon.hexString2RGBA(inString) + -- enter with "#FF0020" (RGB) or "#FF00AB99" RGBA + -- check if it starts with # + if not inString then return nil end + if #inString ~= 7 and #inString ~=9 then return nil end + if inString:sub(1, 1) ~= "#" then return nil end + inString = inString:lower() + local red = tonumber("0x" .. inString:sub(2,3)) + if not red then red = 0 end + local green = tonumber("0x" .. inString:sub(4,5)) + if not green then green = 0 end + local blue = tonumber("0x" .. inString:sub(6,7)) + if not blue then blue = 0 end + local alpha = 255 + if #inString == 9 then + alpha = tonumber("0x" .. inString:sub(8,9)) + end + if not alpha then alpha = 0 end + return {red/255, green/255, blue/255, alpha/255} +end + + +-- +-- Player handling +-- +function dcsCommon.playerName2Coalition(playerName) + if not playerName then return 0 end + local factions = {1,2} + for idx, theFaction in pairs(factions) do + local players = coalition.getPlayers(theFaction) + for idy, theUnit in pairs(players) do + local upName = theUnit:getPlayerName() + if upName == playerName then return theFaction end + end + end + return 0 +end + -- -- iterators -- diff --git a/modules/missionRestart.lua b/modules/missionRestart.lua index 0cfe387..afba554 100644 --- a/modules/missionRestart.lua +++ b/modules/missionRestart.lua @@ -1,6 +1,10 @@ missionRestart = {} +missionRestart.version = "1.0.0" missionRestart.restarting = false - +-- +-- Restart this mission, irrespective of its name +-- Only works if run as multiplayer (sends commands to the server) +-- function missionRestart.restart() if missionRestart.restarting then return end diff --git a/modules/sittingDucks standalone.lua b/modules/sittingDucks standalone.lua new file mode 100644 index 0000000..402f1f1 --- /dev/null +++ b/modules/sittingDucks standalone.lua @@ -0,0 +1,75 @@ +sittingDucks = {} +sittingDucks.verbose = false +sittingDucks.version = "1.0.0" +sittingDucks.ssbDisabled = 100 -- must match the setting of SSB, usually 100 +sittingDucks.resupplyTime = -1 -- seconds until "reinforcements" reopen the slot, set to -1 to turn off, 3600 is one hour + +-- +-- Destroying a client stand-in on an airfield will block that +-- Slot for players. Multiplayer only +-- WARNING: ENTIRE GROUP will be blocked when one aircraft is destroyed +-- +-- MULTIPLAYER-ONLY. REQUIRES (on the server): +-- 1) SSB running on the server AND +-- 2) set SSB.kickReset = false +-- + +function sittingDucks:onEvent(event) + if not event then return end + if not event.id then return end + if not event.initiator then return end + + -- home in on the kill event + if event.id == 8 then -- dead event + local theUnit = event.initiator + local deadName = theUnit:getName() + if not deadName then return end + -- look at stopGap's collection of stand-ins + for gName, staticGroup in pairs (stopGap.standInGroups) do + for uName, aStatic in pairs(staticGroup) do + if uName == deadName then -- yup, a stand-in. block entire group + local blockState = sittingDucks.ssbDisabled + trigger.action.setUserFlag(gName, blockState) + -- tell cfxSSBClient as well - if it's loaded + if cfxSSBClient and cfxSSBClient.slotState then + cfxSSBClient.slotState[gName] = blockState + end + if sittingDucks.verbose then + trigger.action.outText("SittingDuck: in group <" .. gName .. "> unit <" .. uName .. "> was destroyed on the ground, group blocked.", 30) + end + if sittingDucks.resupplyTime > 0 then + timer.scheduleFunction(sittingDucks.resupply, gName, timer.getTime() + sittingDucks.resupplyTime) + end + return + end + end + end + end + +end + +-- re-supply: enable slots after some time +function sittingDucks.resupply(args) + local gName = args + trigger.action.setUserFlag(gName, 0) + if cfxSSBClient and cfxSSBClient.slotState then + cfxSSBClient.slotState[gName] = 0 + end + if stopGap.standInGroups[gName] then -- should not happen, just in case + stopGap.removeStaticGapGroupNamed(gName) + end + if sittingDucks.verbose then + trigger.action.outText("SittingDuck: group <" .. gName .. "> re-supplied, slots reopened.", 30) + end +end + +-- make sure stopGap is available +if stopGap and stopGap.start then + trigger.action.setUserFlag("SSB",100) + world.addEventHandler(sittingDucks) + trigger.action.outText("Sitting Ducks v" .. sittingDucks.version .. " running, SSB enabled", 30) +else + trigger.action.outText("Sitting Ducks requires stopGap to run", 30) +end + + diff --git a/modules/sittingDucks.lua b/modules/sittingDucks.lua new file mode 100644 index 0000000..a333043 --- /dev/null +++ b/modules/sittingDucks.lua @@ -0,0 +1,148 @@ +sittingDucks = {} +sittingDucks.verbose = false +sittingDucks.version = "1.0.0" +sittingDucks.ssbDisabled = 100 -- must match the setting of SSB, usually 100 +sittingDucks.resupplyTime = -1 -- seconds until "reinforcements" reopen the slot, set to -1 to turn off, 3600 is one hour +sittingDucks.requiredLibs = { + "dcsCommon", + "cfxZones", + "stopGap", +} +-- +-- Destroying a client stand-in on an airfield will block that +-- Slot for players. Multiplayer only +-- WARNING: ENTIRE GROUP will be blocked when one aircraft is destroyed +-- +-- MULTIPLAYER-ONLY. REQUIRES (on the server): +-- 1) SSB running on the server AND +-- 2) set SSB.kickReset = false +-- + +function sittingDucks:onEvent(event) + if not event then return end + if not event.id then return end + if not event.initiator then return end + + if not sittingDucks.enabled then return end -- olny look if we are turned on + + -- home in on the kill event + if event.id == 8 then -- dead event + local theUnit = event.initiator + local deadName = theUnit:getName() + if not deadName then return end + -- look at stopGap's collection of stand-ins + for gName, staticGroup in pairs (stopGap.standInGroups) do + for uName, aStatic in pairs(staticGroup) do + if uName == deadName then -- yup, a stand-in. block entire group + local blockState = sittingDucks.ssbDisabled + trigger.action.setUserFlag(gName, blockState) + -- tell cfxSSBClient as well - if it's loaded + if cfxSSBClient and cfxSSBClient.slotState then + cfxSSBClient.slotState[gName] = blockState + end + if sittingDucks.verbose then + trigger.action.outText("SittingDucks: in group <" .. gName .. "> unit <" .. uName .. "> was destroyed on the ground, group blocked.", 30) + end + if sittingDucks.resupplyTime > 0 then + timer.scheduleFunction(sittingDucks.resupply, gName, timer.getTime() + sittingDucks.resupplyTime) + end + return + end + end + end + end + +end + +-- re-supply: enable slots after some time +function sittingDucks.resupply(args) + local gName = args + trigger.action.setUserFlag(gName, 0) + if cfxSSBClient and cfxSSBClient.slotState then + cfxSSBClient.slotState[gName] = 0 + end + if stopGap.standInGroups[gName] then -- should not happen, just in case + stopGap.removeStaticGapGroupNamed(gName) + end + if sittingDucks.verbose then + trigger.action.outText("SittingDucks: group <" .. gName .. "> re-supplied, slots reopened.", 30) + end +end + +-- +-- Update +-- +-- +function sittingDucks.update() + -- check every second. + timer.scheduleFunction(sittingDucks.update, {}, timer.getTime() + 1) + + -- check if signal for on? or off? + if sittingDucks.turnOn and cfxZones.testZoneFlag(sittingDucks, sittingDucks.turnOnFlag, sittingDucks.triggerMethod, "lastTurnOnFlag") then + sittingDucks.enabled = true + end + + if sittingDucks.turnOff and cfxZones.testZoneFlag(sittingDucks, sittingDucks.turnOffFlag, sittingDucks.triggerMethod, "lastTurnOffFlag") then + sittingDucks.enabled = false + end + +end + +-- +-- Read Config & start +-- +sittingDucks.name = "sittingDucksConfig" -- cfxZones compatibility here +function sittingDucks.readConfigZone(theZone) + -- currently nothing to do + sittingDucks.verbose = theZone.verbose + sittingDucks.resupplyTime = cfxZones.getNumberFromZoneProperty(theZone, "resupplyTime", -1) + sittingDucks.enabled = cfxZones.getBoolFromZoneProperty(theZone, "onStart", true) + sittingDucks.triggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + if cfxZones.hasProperty(theZone, "on?") then + sittingDucks.turnOnFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "*") + sittingDucks.lastTurnOnFlag = trigger.misc.getUserFlag(sittingDucks.turnOnFlag) + end + if cfxZones.hasProperty(theZone, "off?") then + sittingDucks.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*") + sittingDucks.lastTurnOffFlag = trigger.misc.getUserFlag(sittingDucks.turnOffFlag) + end + + if sittingDucks.verbose then + trigger.action.outText("+++sitD: config read, verbose = YES", 30) + if sittingDucks.enabled then + trigger.action.outText("+++sitD: enabled", 30) + else + trigger.action.outText("+++sitD: turned off", 30) + end + end +end + + +function sittingDucks.start() + if not dcsCommon.libCheck("cfx Sitting Ducks", + stopGap.requiredLibs) + then return false end + + local theZone = cfxZones.getZoneByName("sittingDucksConfig") + if not theZone then + theZone = cfxZones.createSimpleZone("sittingDucksConfig") + end + sittingDucks.readConfigZone(theZone) + + -- turn on SSB + trigger.action.setUserFlag("SSB",100) + + -- let's get set up + world.addEventHandler(sittingDucks) -- event handler in place + timer.scheduleFunction(sittingDucks.update, {}, timer.getTime() + 1) + + trigger.action.outText("Sitting Ducks v" .. sittingDucks.version .. " running, SSB enabled", 30) + return true +end + +if not sittingDucks.start() then + trigger.action.outText("Sitting Ducks failed to start up.", 30) + sittingDucks = {} +end + + diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index 7ed83e7..4a8d545 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,5 +1,5 @@ stopGap = {} -stopGap.version = "1.0.4" +stopGap.version = "1.0.5" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" @@ -35,6 +35,7 @@ stopGap.requiredLibs = { - stopGap Zones 1.0.3 - server plug-in logic 1.0.4 - player units or groups that end in '-sg' are not stop-gapped + 1.0.5 - triggerMethod --]]-- stopGap.standInGroups = {} @@ -216,14 +217,14 @@ function stopGap.update() timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) -- check if signal for on? or off? - if stopGap.turnOn and cfxZones.testZoneFlag(stopGap, stopGap.turnOnFlag, "change", "lastTurnOnFlag") then + if stopGap.turnOn and cfxZones.testZoneFlag(stopGap, stopGap.turnOnFlag, stopGap.triggerMethod, "lastTurnOnFlag") then if not stopGap.enabled then stopGap.turnOn() end stopGap.enabled = true end - if stopGap.turnOff and cfxZones.testZoneFlag(stopGap, stopGap.turnOffFlag, "change", "lastTurnOffFlag") then + if stopGap.turnOff and cfxZones.testZoneFlag(stopGap, stopGap.turnOffFlag, stopGap.triggerMethod, "lastTurnOffFlag") then if stopGap.enabled then stopGap.turnOff() end @@ -315,7 +316,7 @@ function stopGap.readConfigZone(theZone) stopGap.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*") stopGap.lastTurnOffFlag = trigger.misc.getUserFlag(stopGap.turnOffFlag) end - + stopGap.triggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") if stopGap.verbose then trigger.action.outText("+++StopG: config read, verbose = YES", 30) if stopGap.enabled then diff --git a/tutorial & demo missions/demo - Caucasus Hangar.miz b/tutorial & demo missions/demo - Caucasus Hangar.miz new file mode 100644 index 0000000..b8a70d4 Binary files /dev/null and b/tutorial & demo missions/demo - Caucasus Hangar.miz differ diff --git a/tutorial & demo missions/demo - Later Score.miz b/tutorial & demo missions/demo - Later Score.miz index a2ca3ec..90c254b 100644 Binary files a/tutorial & demo missions/demo - Later Score.miz and b/tutorial & demo missions/demo - Later Score.miz differ diff --git a/tutorial & demo missions/demo - sitting ducks in a barrel.miz b/tutorial & demo missions/demo - sitting ducks in a barrel.miz new file mode 100644 index 0000000..bd040de Binary files /dev/null and b/tutorial & demo missions/demo - sitting ducks in a barrel.miz differ