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 = {}
cfxOwnedZones.version = "2.0.0" cfxOwnedZones.version = "2.0.1"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -21,6 +21,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
- heloCap option - heloCap option
- fixWingCap option - fixWingCap option
- filter water owned zones for groundTroops - filter water owned zones for groundTroops
2.0.1 - RGBA colors can be entered hex style #ff340799
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -839,7 +840,5 @@ end
dont count zones that cant be conquered for allBlue/allRed dont count zones that cant be conquered for allBlue/allRed
define color with #FF008080
--]]-- --]]--

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {} cfxPlayerScore = {}
cfxPlayerScore.version = "2.0.1" cfxPlayerScore.version = "2.1.1"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.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 - immediate awarding of all negative scores, even if deferred
2.0.1 - corrected access to nowString() 2.0.1 - corrected access to nowString()
- more robust config reading - 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 "dcsCommon", -- this is doing score keeping
"cfxZones", -- zones for config "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.deferred = false -- on deferred, we only award after landing, and erase on any form of re-slot
cfxPlayerScore.delayAfterLanding = 10 -- seconds after landing cfxPlayerScore.delayAfterLanding = 10 -- seconds after landing
cfxPlayerScore.safeZones = {} -- safe zones to land in cfxPlayerScore.safeZones = {} -- safe zones to land in
cfxPlayerScore.featZones = {} -- zones that define feats cfxPlayerScore.featZones = {} -- zones that define feats
cfxPlayerScore.killZones = {} -- when set, kills only count here cfxPlayerScore.killZones = {} -- when set, kills only count here
-- typeScore: dictionary sorted by typeString for score -- typeScore: dictionary sorted by typeString for score
-- extend to add more types. It is used by unitType2score to -- extend to add more types. It is used by unitType2score to
-- determine the base unit score -- determine the base unit score
@ -107,7 +117,6 @@ cfxPlayerScore.train = 5
cfxPlayerScore.landing = 0 -- if > 0 it scores as feat cfxPlayerScore.landing = 0 -- if > 0 it scores as feat
cfxPlayerScore.unit2player = {} -- lookup and reverse look-up cfxPlayerScore.unit2player = {} -- lookup and reverse look-up
--cfxPlayerScore.player2unit = {} -- to detect death and destruction
function cfxPlayerScore.addSafeZone(theZone) function cfxPlayerScore.addSafeZone(theZone)
theZone.scoreSafe = cfxZones.getCoalitionFromZoneProperty(theZone, "scoreSafe", 0) 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 -- and location of victim for kill
-- coa is coalition of landing unit -- coa is coalition of landing unit
-- and coalition of killer for kill -- and coalition of killer for kill
-- trigger.action.outText("enter feat check for <" .. featType .. ">", 30)
if not coa then coa = 0 end if not coa then coa = 0 end
if not featType then featType = "KILL" end if not featType then featType = "KILL" end
featType = string.upper(featType) featType = string.upper(featType)
local theFeats = {} local theFeats = {}
for idx, theZone in pairs(cfxPlayerScore.featZones) do for idx, theZone in pairs(cfxPlayerScore.featZones) do
-- trigger.action.outText("featcheck: <" .. theZone.name .. ">", 30)
local canAward = true local canAward = true
-- check if it can be awarded -- check if it can be awarded
if theZone.featNum == 0 then if theZone.featNum == 0 then
canAward = false canAward = false
-- trigger.action.outText(" - failed featNum", 30)
end end
if theZone.featType ~= featType then if theZone.featType ~= featType then
canAward = false canAward = false
--trigger.action.outText(" - failed type check (look for <" .. featType .. ">, got <" .. theZone.featType .. ">", 30)
end end
if not (theZone.coalition == 0 or theZone.coalition == coa) then if not (theZone.coalition == 0 or theZone.coalition == coa) then
canAward = false canAward = false
-- trigger.action.outText(" - failed coa check ", 30)
end end
if featType == "PVP" then if featType == "PVP" then
@ -191,21 +196,17 @@ function cfxPlayerScore.featsForLocation(name, loc, coa, featType, killer, victi
if not cfxZones.pointInZone(loc, theZone) then if not cfxZones.pointInZone(loc, theZone) then
canAward = false canAward = false
-- trigger.action.outText(" - failed loc check ", 30)
end end
if theZone.ppOnce then if theZone.ppOnce then
if theZone.awardedTo[name] then if theZone.awardedTo[name] then
canAward = false canAward = false
--trigger.action.outText(" - already awarded fail ", 30)
end end
end end
if canAward then if canAward then
table.insert(theFeats, theZone) -- jupp, add it table.insert(theFeats, theZone) -- jupp, add it
--trigger.action.outText(" can award", 30)
else else
--trigger.action.outText("FAIL.", 30)
end end
end end
@ -240,7 +241,6 @@ function cfxPlayerScore.preprocessWildcards(inMsg, aUnit, aVictim)
theMsg = theMsg:gsub("<type>", aVictim:getTypeName()) theMsg = theMsg:gsub("<type>", aVictim:getTypeName())
-- victim may not have group. guard against that -- victim may not have group. guard against that
-- happens if unit 'cooks off' -- happens if unit 'cooks off'
--local gName = "(unknown)"
local aGroup = nil local aGroup = nil
if aVictim.getGroup then if aVictim.getGroup then
aVictim:getGroup() aVictim:getGroup()
@ -290,9 +290,8 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
inName = tostring(inName) inName = tostring(inName)
end 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 -- cooking off, so first thing we need to do is do a name check
local objectScore = cfxPlayerScore.typeScore[inName] local objectScore = cfxPlayerScore.typeScore[inName]
if not objectScore then if not objectScore then
-- try the type desc -- try the type desc
@ -384,6 +383,20 @@ function cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score)
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName) local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
thePlayerScore.score = thePlayerScore.score + score thePlayerScore.score = thePlayerScore.score + score
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) 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 return thePlayerScore.score
end end
@ -395,10 +408,6 @@ function cfxPlayerScore.updateScoreForPlayer(playerName, score)
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write-through. why? because it may be a new entry. cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write-through. why? because it may be a new entry.
return thePlayerScore.score -- this is the old score!!! return thePlayerScore.score -- this is the old score!!!
end end
--local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
--thePlayerScore.score = thePlayerScore.score + score
--cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
--return thePlayerScore.score
-- now write immediately -- now write immediately
return cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score) return cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score)
end end
@ -524,13 +533,34 @@ function cfxPlayerScore.scoreTextForPlayerNamed(playerName)
return cfxPlayerScore.playerScore2text(thePlayerScore) return cfxPlayerScore.playerScore2text(thePlayerScore)
end 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) function cfxPlayerScore.scoreTextForAllPlayers(ranked)
if not ranked then ranked = false end if not ranked then ranked = false end
local theText = "" local theText = ""
local isFirst = true local isFirst = true
local theScores = cfxPlayerScore.playerScore local theScores = cfxPlayerScore.playerScore
if cfxPlayerScore.verbose then 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 end
if ranked then if ranked then
table.sort(theScores, function(left, right) return left.score < right.score end ) table.sort(theScores, function(left, right) return left.score < right.score end )
@ -548,6 +578,17 @@ function cfxPlayerScore.scoreTextForAllPlayers(ranked)
isFirst = false isFirst = false
rank = rank + 1 rank = rank + 1
end 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 return theText
end 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) trigger.action.outTextForCoalition(killSide, "Killscore: " .. theScore .. ", now " .. thePlayerRecord.scoreaccu .. " waiting for " .. killerName .. ", awarded after landing", 30)
else -- negative score or not deferred else -- negative score or not deferred
trigger.action.outTextForCoalition(killSide, "Killscore: " .. theScore .. " for a total of " .. playerScore .. " for " .. killerName, 30) 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 end
end end
@ -937,20 +982,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
-- only continue if there is anything to award -- only continue if there is anything to award
local killSize = dcsCommon.getSizeOfTable(theScore.killQueue) local killSize = dcsCommon.getSizeOfTable(theScore.killQueue)
local featSize = dcsCommon.getSizeOfTable(theScore.featQueue) 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 if cfxPlayerScore.verbose then
trigger.action.outText("+++pScr: prepping deferred score for <" .. playerName ..">", 30) trigger.action.outText("+++pScr: prepping deferred score for <" .. playerName ..">", 30)
@ -964,9 +995,17 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
local isSafe = false local isSafe = false
for idx, theZone in pairs(cfxPlayerScore.safeZones) do 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
-- 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 if cfxZones.pointInZone(loc, theZone) then
isSafe = true isSafe = true
end 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
end end
@ -1015,6 +1054,7 @@ function cfxPlayerScore.scheduledAward(args)
local isSafe = false local isSafe = false
for idx, theZone in pairs(cfxPlayerScore.safeZones) do 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 if cfxZones.pointInZone(loc, theZone) then
isSafe = true isSafe = true
end end
@ -1028,6 +1068,11 @@ function cfxPlayerScore.scheduledAward(args)
local theScore = cfxPlayerScore.getPlayerScore(playerName) 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 if dcsCommon.getSizeOfTable(theScore.killQueue) < 1 and
dcsCommon.getSizeOfTable(theScore.featQueue) < 1 and dcsCommon.getSizeOfTable(theScore.featQueue) < 1 and
theScore.scoreaccu < 1 then theScore.scoreaccu < 1 then
@ -1042,9 +1087,10 @@ function cfxPlayerScore.scheduledAward(args)
-- when we get here we award all scores, kills, and feats -- when we get here we award all scores, kills, and feats
local desc = "\nPlayer " .. playerName .. " is awarded:\n" local desc = "\nPlayer " .. playerName .. " is awarded:\n"
-- score and total score -- score and total score
if theScore.scoreaccu > 0 then if theScore.scoreaccu > 0 then -- remember: negatives are immediate
theScore.score = theScore.score + theScore.scoreaccu theScore.score = theScore.score + theScore.scoreaccu
desc = desc .. " score: " .. theScore.scoreaccu .. " for a new total of " .. theScore.score .. "\n" desc = desc .. " score: " .. theScore.scoreaccu .. " for a new total of " .. theScore.score .. "\n"
cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu
theScore.scoreaccu = 0 theScore.scoreaccu = 0
hasAward = true hasAward = true
end end
@ -1074,6 +1120,10 @@ function cfxPlayerScore.scheduledAward(args)
end end
theScore.featQueue = {} theScore.featQueue = {}
if cfxPlayerScore.reportCoalition then
desc = desc .. "\nCoalition Total: " .. cfxPlayerScore.coalitionScore[playerSide]
end
-- output score -- output score
desc = desc .. "\n" desc = desc .. "\n"
if hasAward then if hasAward then
@ -1129,7 +1179,8 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
local playerName = thePlayerUnit:getPlayerName() local playerName = thePlayerUnit:getPlayerName()
local theScore = cfxPlayerScore.getPlayerScore(playerName) local theScore = cfxPlayerScore.getPlayerScore(playerName)
-- now re-init feat and score queues -- 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) trigger.action.outTextForCoalition(playerSide, "Player " .. playerName .. ", score of <" .. theScore.scoreaccu .. "> points discarded.", 30)
end end
theScore.scoreaccu = 0 theScore.scoreaccu = 0
@ -1209,6 +1260,11 @@ function cfxPlayerScore.readConfigZone(theZone)
cfxPlayerScore.reportScore = cfxZones.getBoolFromZoneProperty(theZone, "reportScore", true) cfxPlayerScore.reportScore = cfxZones.getBoolFromZoneProperty(theZone, "reportScore", true)
cfxPlayerScore.reportFeats = cfxZones.getBoolFromZoneProperty(theZone, "reportFeats", 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 end
-- --
@ -1220,6 +1276,7 @@ function cfxPlayerScore.saveData()
local theScore = dcsCommon.clone(cfxPlayerScore.playerScore) local theScore = dcsCommon.clone(cfxPlayerScore.playerScore)
theData.theScore = theScore theData.theScore = theScore
-- build feat zone list -- build feat zone list
theData.coalitionScore = dcsCommon.clone(cfxPlayerScore.coalitionScore)
local featZones = {} local featZones = {}
for idx, theZone in pairs(cfxPlayerScore.featZones) do for idx, theZone in pairs(cfxPlayerScore.featZones) do
local theFeat = {} local theFeat = {}
@ -1243,6 +1300,9 @@ function cfxPlayerScore.loadData()
local theScore = theData.theScore local theScore = theData.theScore
cfxPlayerScore.playerScore = theScore cfxPlayerScore.playerScore = theScore
if theData.coalitionScore then
cfxPlayerScore.coalitionScore = theData.coalitionScore
end
local featData = theData.featData local featData = theData.featData
if featData then if featData then
for name, data in pairs(featData) do for name, data in pairs(featData) do

View File

@ -1,5 +1,5 @@
cfxPlayerScoreUI = {} cfxPlayerScoreUI = {}
cfxPlayerScoreUI.version = "2.0.1" cfxPlayerScoreUI.version = "2.1.0"
cfxPlayerScoreUI.verbose = false cfxPlayerScoreUI.verbose = false
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -7,11 +7,22 @@ cfxPlayerScoreUI.verbose = false
- 1.0.3 - module check - 1.0.3 - module check
- 2.0.0 - removed cfxPlayer dependency, handles own commands - 2.0.0 - removed cfxPlayer dependency, handles own commands
- 2.0.1 - late start capability - 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.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) function cfxPlayerScoreUI.redirectCommandX(args)
timer.scheduleFunction(cfxPlayerScoreUI.doCommandX, args, timer.getTime() + 0.1) timer.scheduleFunction(cfxPlayerScoreUI.doCommandX, args, timer.getTime() + 0.1)
end end
@ -22,14 +33,24 @@ function cfxPlayerScoreUI.doCommandX(args)
local what = args[3] -- "score" or other commands local what = args[3] -- "score" or other commands
local theGroup = Group.getByName(groupName) local theGroup = Group.getByName(groupName)
local gid = theGroup:getID() local gid = theGroup:getID()
local coa = theGroup:getCoalition()
if not cfxPlayerScore.scoreTextForPlayerNamed then if not cfxPlayerScore.scoreTextForPlayerNamed then
trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30) trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30)
return return
end 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.outTextForGroup(gid, desc, 30)
trigger.action.outSoundForGroup(gid, "Quest Snare 3.wav") trigger.action.outSoundForGroup(gid, cfxPlayerScoreUI.soundFile)
end end
-- --
@ -59,8 +80,18 @@ function cfxPlayerScore.processPlayerUnit(theUnit)
-- we need to install a group menu item for scores. -- we need to install a group menu item for scores.
-- will persist through death -- will persist through death
local commandTxt = "Show Score / Kills" local commandTxt = "Show Score / Kills"
local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, nil, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"} ) local theMenu = missionCommands.addSubMenuForGroup(gid, "Show Score", nil)
cfxPlayerScoreUI.rootCommands[groupName] = theCommand 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 if cfxPlayerScoreUI.verbose then
trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30) trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30)
@ -78,6 +109,9 @@ end
-- Start -- Start
-- --
function cfxPlayerScoreUI.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 -- install the event handler for new player planes
world.addEventHandler(cfxPlayerScoreUI) world.addEventHandler(cfxPlayerScoreUI)
-- process all existing players (late start) -- process all existing players (late start)

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "3.1.0" cfxZones.version = "3.1.1"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -129,6 +129,8 @@ cfxZones.version = "3.1.0"
- 3.0.9 - new getFlareColorStringFromZoneProperty() - 3.0.9 - new getFlareColorStringFromZoneProperty()
- 3.1.0 - new getRGBVectorFromZoneProperty() - 3.1.0 - new getRGBVectorFromZoneProperty()
new getRGBAVectorFromZoneProperty() new getRGBAVectorFromZoneProperty()
- 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format
- owner for all, default 0
--]]-- --]]--
cfxZones.verbose = false 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 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 if #defaultVal ~=4 then defaultVal = {1.0, 1.0, 1.0, 1.0} end
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, "") 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 sVec = dcsCommon.splitString(s, ",")
local nVec = {} local nVec = {}
for i = 1, 4 do for i = 1, 4 do
@ -2841,8 +2850,10 @@ function cfxZones.init()
cfxZones.readFromDCS(true) -- true: erase old cfxZones.readFromDCS(true) -- true: erase old
-- pre-read zone owner for all zones -- pre-read zone owner for all zones
local pZones = cfxZones.zonesWithProperty("owner") -- much like verbose, all zones have owner
for n, aZone in pairs(pZones) do -- 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) aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)
end end

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.8.7" dcsCommon.version = "2.8.8"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -150,7 +150,9 @@ dcsCommon.version = "2.8.7"
2.8.7 - new flareColor2Num() 2.8.7 - new flareColor2Num()
- new flareColor2Text() - new flareColor2Text()
- new iteratePlayers() - new iteratePlayers()
2.8.8 - new hexString2RGBA()
- new playerName2Coalition()
- new coalition2Text()
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -2955,6 +2957,14 @@ function dcsCommon.coalition2county(inCoalition)
end 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) function dcsCommon.latLon2Text(lat, lon)
-- inspired by mist, thanks Grimes! -- inspired by mist, thanks Grimes!
-- returns two strings: lat and lon -- returns two strings: lat and lon
@ -3340,6 +3350,47 @@ function dcsCommon.spellString(inString)
return res return res
end 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 -- iterators
-- --

View File

@ -1,6 +1,10 @@
missionRestart = {} missionRestart = {}
missionRestart.version = "1.0.0"
missionRestart.restarting = false missionRestart.restarting = false
--
-- Restart this mission, irrespective of its name
-- Only works if run as multiplayer (sends commands to the server)
--
function missionRestart.restart() function missionRestart.restart()
if missionRestart.restarting then return end 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 = {}
stopGap.version = "1.0.4" stopGap.version = "1.0.5"
stopGap.verbose = false stopGap.verbose = false
stopGap.ssbEnabled = true stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg" stopGap.ignoreMe = "-sg"
@ -35,6 +35,7 @@ stopGap.requiredLibs = {
- stopGap Zones - stopGap Zones
1.0.3 - server plug-in logic 1.0.3 - server plug-in logic
1.0.4 - player units or groups that end in '-sg' are not stop-gapped 1.0.4 - player units or groups that end in '-sg' are not stop-gapped
1.0.5 - triggerMethod
--]]-- --]]--
stopGap.standInGroups = {} stopGap.standInGroups = {}
@ -216,14 +217,14 @@ function stopGap.update()
timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1)
-- check if signal for on? or off? -- 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 if not stopGap.enabled then
stopGap.turnOn() stopGap.turnOn()
end end
stopGap.enabled = true stopGap.enabled = true
end 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 if stopGap.enabled then
stopGap.turnOff() stopGap.turnOff()
end end
@ -315,7 +316,7 @@ function stopGap.readConfigZone(theZone)
stopGap.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>") stopGap.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>")
stopGap.lastTurnOffFlag = trigger.misc.getUserFlag(stopGap.turnOffFlag) stopGap.lastTurnOffFlag = trigger.misc.getUserFlag(stopGap.turnOffFlag)
end end
stopGap.triggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
if stopGap.verbose then if stopGap.verbose then
trigger.action.outText("+++StopG: config read, verbose = YES", 30) trigger.action.outText("+++StopG: config read, verbose = YES", 30)
if stopGap.enabled then if stopGap.enabled then

Binary file not shown.