Version 1.3.2

Sitting Ducks
PlayerScore improvements
Colors for Owned Zones
This commit is contained in:
Christian Franz 2023-06-07 13:40:37 +02:00
parent 3d058a12f2
commit 3b6f22ab78
14 changed files with 444 additions and 61 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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 = {
@ -839,7 +840,5 @@ end
dont count zones that cant be conquered for allBlue/allRed
define color with #FF008080
--]]--

View File

@ -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("<type>", 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
@ -1015,6 +1054,7 @@ function cfxPlayerScore.scheduledAward(args)
local isSafe = false
for idx, theZone in pairs(cfxPlayerScore.safeZones) do
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

View File

@ -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)

View File

@ -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

View File

@ -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
--

View File

@ -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

View File

@ -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

148
modules/sittingDucks.lua Normal file
View File

@ -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?", "*<none>")
sittingDucks.lastTurnOnFlag = trigger.misc.getUserFlag(sittingDucks.turnOnFlag)
end
if cfxZones.hasProperty(theZone, "off?") then
sittingDucks.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>")
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

View File

@ -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?", "*<none>")
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

Binary file not shown.