diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 22f110d..c77c593 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 e444d35..61b60ba 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxMX.lua b/modules/cfxMX.lua index 1993566..834c88b 100644 --- a/modules/cfxMX.lua +++ b/modules/cfxMX.lua @@ -1,5 +1,5 @@ cfxMX = {} -cfxMX.version = "1.2.5" +cfxMX.version = "1.2.6" cfxMX.verbose = false --[[-- Mission data decoder. Access to ME-built mission structures @@ -25,6 +25,8 @@ cfxMX.verbose = false - groupCoalitionByName 1.2.4 - playerUnit2Group cross index 1.2.5 - unitIDbyName index added + 1.2.6 - cfxMX.allTrainsByName + - train carve-outs for vehicles --]]-- cfxMX.groupNamesByID = {} cfxMX.groupIDbyName = {} @@ -39,6 +41,7 @@ cfxMX.allHeloByName = {} cfxMX.allGroundByName = {} cfxMX.allSeaByName = {} cfxMX.allStaticByName = {} +cfxMX.allTrainsByName = {} cfxMX.playerGroupByName = {} -- returns data only if a player is in group cfxMX.playerUnitByName = {} -- returns data only if this is a player unit @@ -76,6 +79,7 @@ function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" + -- note: trains are 'vehicle' here then -- (so it's not id or name) local category = obj_type_name if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group! @@ -86,6 +90,9 @@ function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) if not fetchOriginal then theGroup = dcsCommon.clone(group_data) end + -- train carve-out: if first unit's type == "Train", change + -- category to "train" + if group_data.units[1] and group_data.units[1].type == "Train" then category = "train" end return theGroup, category, countryID end end @@ -200,6 +207,7 @@ function cfxMX.createCrossReferences() obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" -- what about "cargo"? + -- not that trains appear as 'vehicle' then -- (so it's not id or name) local category = obj_type_name if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group! @@ -213,6 +221,11 @@ function cfxMX.createCrossReferences() linkUnit = group_data.route.points[1].linkUnit cfxMX.linkByName[aName] = linkUnit end + if group_data.units[1] and group_data.units[1].type == "Train" then + category = "train" + obj_type_name = "train" + end + cfxMX.groupTypeByName[aName] = category cfxMX.groupNamesByID[aID] = aName cfxMX.groupIDbyName[aName] = aID @@ -231,6 +244,8 @@ function cfxMX.createCrossReferences() cfxMX.allGroundByName[aName] = group_data elseif obj_type_name == "static" then cfxMX.allStaticByName[aName] = group_data + elseif obj_type_name == "train" then + cfxMX.allTrainsByName[aName] = group_data else -- should be impossible, but still trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30) @@ -268,6 +283,7 @@ function cfxMX.catText2ID(inText) if c == "vehicle" then outCat = 2 end if c == "train" then outCat = 4 end if c == "static" then outCat = -1 end + --trigger.action.outText("cat2text: in <" .. inText .. "> out <" .. outCat .. ">", 30) return outCat end diff --git a/modules/cfxPlayerScore.lua b/modules/cfxPlayerScore.lua index dd1499b..28eae86 100644 --- a/modules/cfxPlayerScore.lua +++ b/modules/cfxPlayerScore.lua @@ -1,5 +1,5 @@ cfxPlayerScore = {} -cfxPlayerScore.version = "2.1.1" +cfxPlayerScore.version = "2.2.0" cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav" @@ -80,6 +80,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite - new scoreSummaryForPlayersOfCoalition() - new noGrief option in config - improved guards when checking ownership (nil zone owner) + 2.0.0 - score flags for red and blue --]]-- @@ -1396,6 +1397,38 @@ function cfxPlayerScore.update() end end + -- check score flags + if cfxPlayerScore.blueTriggerFlags then + local coa = 2 + for tName, tVal in pairs(cfxPlayerScore.blueTriggerFlags) do + local newVal = trigger.misc.getUserFlag(tName) + if tVal ~= newVal then + -- score! + cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName] + cfxPlayerScore.blueTriggerFlags[tName] = newVal + if cfxPlayerScore.announcer then + trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30) + trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound) + end + end + end + end + if cfxPlayerScore.redTriggerFlags then + local coa = 1 + for tName, tVal in pairs(cfxPlayerScore.redTriggerFlags) do + local newVal = trigger.misc.getUserFlag(tName) + if tVal ~= newVal then + -- score! + + cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName] + cfxPlayerScore.redTriggerFlags[tName] = newVal + if cfxPlayerScore.announcer then + trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30) + trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound) + end + end + end + end end -- -- start @@ -1418,10 +1451,42 @@ function cfxPlayerScore.start() trigger.action.outText("+++scr: read score table", 30) end + -- read score tiggers and values + cfxPlayerScore.redTriggerFlags = nil + cfxPlayerScore.blueTriggerFlags = nil + local theZone = cfxZones.getZoneByName("redScoreFlags") + if theZone then + -- read flags into redTriggerScore + cfxPlayerScore.redTriggerScore = cfxZones.getAllZoneProperties(theZone, false, true) -- use case, all numbers + -- init their flag handlers + cfxPlayerScore.redTriggerFlags = {} + trigger.action.outText("+++pScr: read RED score table", 30) + for tName, tScore in pairs(cfxPlayerScore.redTriggerScore) do + if tScore == 0 then + trigger.action.outText("+++pScr: WARNING - RED triggered score <" .. tName .. "> has zero score value!", 30) + end + cfxPlayerScore.redTriggerFlags[tName] = trigger.misc.getUserFlag(tName) + end + end + local theZone = cfxZones.getZoneByName("blueScoreFlags") + if theZone then + -- read flags into redTriggerScore + cfxPlayerScore.blueTriggerScore = cfxZones.getAllZoneProperties(theZone, false, true) -- case sensitive, numbers only + -- init their flag handlers + cfxPlayerScore.blueTriggerFlags = {} + trigger.action.outText("+++pScr: read BLUE score table", 30) + for tName, tScore in pairs(cfxPlayerScore.blueTriggerScore) do + if tScore == 0 then + trigger.action.outText("+++pScr: WARNING - BLUE triggered score <" .. tName .. "> has zero score value!", 30) + end + cfxPlayerScore.blueTriggerFlags[tName] = trigger.misc.getUserFlag(tName) + end + end + -- now read my config zone local theZone = cfxZones.getZoneByName("playerScoreConfig") if not theZone then - trigger.action.outText("+++scr: no config!", 30) + trigger.action.outText("+++pScr: no config!", 30) theZone = cfxZones.createSimpleZone("playerScoreConfig") end cfxPlayerScore.readConfigZone(theZone) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 857b25b..c3b444e 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "3.1.1" +cfxZones.version = "3.1.2" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -131,6 +131,7 @@ cfxZones.version = "3.1.1" new getRGBAVectorFromZoneProperty() - 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format - owner for all, default 0 +- 3.1.2 - getAllZoneProperties has numbersOnly option --]]-- cfxZones.verbose = false @@ -1947,8 +1948,9 @@ end -- =================== -- -function cfxZones.getAllZoneProperties(theZone, caseInsensitive) -- return as dict +function cfxZones.getAllZoneProperties(theZone, caseInsensitive, numbersOnly) -- return as dict if not caseInsensitive then caseInsensitive = false end + if not numbersOnly then numbersOnly = false end if not theZone then return {} end local dcsProps = theZone.properties -- zone properties in dcs format @@ -1960,7 +1962,12 @@ function cfxZones.getAllZoneProperties(theZone, caseInsensitive) -- return as di local theKey = "dummy" if string.len(theProp.key) > 0 then theKey = theProp.key end if caseInsensitive then theKey = theKey:upper() end - props[theKey] = theProp.value + local v = theProp.value + if numbersOnly then + v = tonumber(v) + if not v then v = 0 end + end + props[theKey] = v end return props end diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 54a8f15..f4c1b7a 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.8.8" +dcsCommon.version = "2.8.9" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -153,6 +153,10 @@ dcsCommon.version = "2.8.8" 2.8.8 - new hexString2RGBA() - new playerName2Coalition() - new coalition2Text() + 2.8.9 - vAdd supports xy and xyz + - vSub supports xy and xyz + - vMultScalar supports xy and xyz + --]]-- -- dcsCommon is a library of common lua functions @@ -2684,7 +2688,9 @@ function dcsCommon.vAdd(a, b) if not b then b = {x = 0, y = 0, z = 0} end r.x = a.x + b.x r.y = a.y + b.y - r.z = a.z + b.z + if a.z and b.z then + r.z = a.z + b.z + end return r end @@ -2694,7 +2700,9 @@ function dcsCommon.vSub(a, b) if not b then b = {x = 0, y = 0, z = 0} end r.x = a.x - b.x r.y = a.y - b.y - r.z = a.z - b.z + if a.z and b.z then + r.z = a.z - b.z + end return r end @@ -2704,7 +2712,9 @@ function dcsCommon.vMultScalar(a, f) if not f then f = 0 end r.x = a.x * f r.y = a.y * f - r.z = a.z * f + if a.z and b.z then + r.z = a.z * f + end return r end diff --git a/modules/stopGaps standalone.lua b/modules/stopGaps standalone.lua index 277690d..56931c6 100644 --- a/modules/stopGaps standalone.lua +++ b/modules/stopGaps standalone.lua @@ -1,8 +1,11 @@ stopGap = {} -stopGap.version = "1.0.4 STANDALONE" +stopGap.version = "1.0.6 STANDALONE" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" +stopGap.spIgnore = "-sp" -- only single-player ignored +stopGap.isMP = false + --[[-- Written and (c) 2023 by Christian Franz @@ -23,7 +26,9 @@ stopGap.ignoreMe = "-sg" 1.0.1 - update / replace statics after slots become free 1.0.3 - server plug-in logic for SSB, sgGUI 1.0.4 - player units or groups that end in '-sg' are not stop-gapped - + 1.0.5 - (DML-only additions) + 1.0.6 - can detect stopGapGUI active on server + - supports "-sp" for single-player only suppress --]]-- stopGap.standInGroups ={} @@ -124,7 +129,7 @@ function stopGap.isGroundStart(theGroup) local sType = land.getSurfaceType(u1) -- has fields x and y if sType == 3 then return false end - if stopGap.verbose then + if false then trigger.action.outText("Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. " land type " .. sType, 30) end return true @@ -134,14 +139,24 @@ function stopGap.createStandInsForMXGroup(group) local allUnits = group.units if group.name:sub(-#stopGap.ignoreMe) == stopGap.ignoreMe then if stopGap.verbose then - trigger.action.outText("<>", 30) + trigger.action.outText("<< '-sg' skipping group " .. group.name .. ">>", 30) + end + return nil + end + if (not stopGap.isMP) and group.name:sub(-#stopGap.spIgnore) == stopGap.spIgnore then + if stopGap.verbose then + trigger.action.outText("<<'-sp' !SP! skipping group " .. group.name .. ">>", 30) end return nil end local theStaticGroup = {} for idx, theUnit in pairs (allUnits) do + local sgMatch = theUnit.name:sub(-#stopGap.ignoreMe) == stopGap.ignoreMe + local spMatch = theUnit.name:sub(-#stopGap.spIgnore) == stopGap.spIgnore + if stopGap.isMP then spMatch = false end -- only single-player if (theUnit.skill == "Client" or theUnit.skill == "Player") - and (theUnit.name:sub(-#stopGap.ignoreMe) ~= stopGap.ignoreMe) + and (not sgMatch) + and (not spMatch) then local theStaticMX = stopGap.staticMXFromUnitMX(group, theUnit) local theStatic = coalition.addStaticObject(theStaticMX.cty, theStaticMX) @@ -189,6 +204,21 @@ function stopGap.initGaps() end end +function stopGap.turnOff() + -- remove all stand-ins + for gName, standIn in pairs (stopGap.standInGroups) do + for name, theStatic in pairs(standIn) do + StaticObject.destroy(theStatic) + end + end + stopGap.standInGroups = {} +end + +function stopGap.turnOn() + -- populate all empty (non-taken) slots with stand-ins + stopGap.initGaps() +end + -- -- event handling -- @@ -234,6 +264,16 @@ function stopGap.update() -- check every 1 second timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) + if not stopGap.isMP then + local sgDetect = trigger.misc.getUserFlag("stopGapGUI") + if sgDetect > 0 then + trigger.action.outText("stopGap: MP activated <" .. sgDetect .. ">, will re-init", 30) + stopGap.turnOff() + stopGap.isMP = true + stopGap.turnOn() + end + end + -- check if slots can be refilled or need to be vacated (MP) for name, theGroup in pairs(stopGap.myGroups) do if not stopGap.standInGroups[name] then @@ -296,6 +336,11 @@ end -- get going -- function stopGap.start() + -- check MP status, usually client is not synched to + -- server, yet so it will initially fail, and re-init in update() + local sgDetect = trigger.misc.getUserFlag("stopGapGUI") + stopGap.isMP = sgDetect > 0 + -- run a cross reference on all mission data for palyer info cfxMX.createCrossReferences() -- fill player slots with static objects @@ -308,7 +353,9 @@ function stopGap.start() timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) -- say hi! - trigger.action.outText("stopGap v" .. stopGap.version .. " running", 30) + local mp = " (SP - <" .. sgDetect .. ">)" + if sgDetect > 0 then mp = " -- MP GUI Detected (" .. sgDetect .. ")!" end + trigger.action.outText("stopGap v" .. stopGap.version .. " running" .. mp, 30) return true end diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index 4a8d545..f3ed22c 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,8 +1,11 @@ stopGap = {} -stopGap.version = "1.0.5" +stopGap.version = "1.0.6" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" +stopGap.spIgnore = "-sp" -- only single-player ignored +stopGap.isMP = false + stopGap.requiredLibs = { "dcsCommon", "cfxZones", @@ -36,6 +39,7 @@ stopGap.requiredLibs = { 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 + 1.0.6 - spIgnore '-sp' --]]-- stopGap.standInGroups = {} @@ -86,12 +90,17 @@ function stopGap.isGroundStart(theGroup) return true end -function stopGap.ignoreMXUnit(theUnit) +function stopGap.ignoreMXUnit(theUnit) -- DML-only local p = {x=theUnit.x, y=0, z=theUnit.y} for idx, theZone in pairs(stopGap.stopGapZones) do if theZone.sgIgnore and cfxZones.pointInZone(p, theZone) then return true end + -- only single-player: exclude units in spIgnore zones + if (not stopGap.isMP) and + theZone.spIgnore and cfxZones.pointInZone(p, theZone) then + return true + end end return false end @@ -104,13 +113,24 @@ function stopGap.createStandInsForMXGroup(group) end return nil end + if (not stopGap.isMP) and group.name:sub(-#stopGap.spIgnore) == stopGap.spIgnore then + if stopGap.verbose then + trigger.action.outText("<<'-sp' !SP! skipping group " .. group.name .. ">>", 30) + end + return nil + end local theStaticGroup = {} for idx, theUnit in pairs (allUnits) do + local sgMatch = theUnit.name:sub(-#stopGap.ignoreMe) == stopGap.ignoreMe + local spMatch = theUnit.name:sub(-#stopGap.spIgnore) == stopGap.spIgnore + local zoneIgnore = stopGap.ignoreMXUnit(theUnit) + if stopGap.isMP then spMatch = false end -- only single-player if (theUnit.skill == "Client" or theUnit.skill == "Player") - and (theUnit.name:sub(-#stopGap.ignoreMe) ~= stopGap.ignoreMe) - and (not stopGap.ignoreMXUnit(theUnit)) - then + and (not sgMatch) + and (not spMatch) + and (not zoneIgnore) + then local theStaticMX = stopGap.staticMXFromUnitMX(group, theUnit) local theStatic = coalition.addStaticObject(theStaticMX.cty, theStaticMX) theStaticGroup[theUnit.name] = theStatic -- remember me @@ -215,8 +235,21 @@ end function stopGap.update() -- check every second. timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) - - -- check if signal for on? or off? + + if not stopGap.isMP then + local sgDetect = trigger.misc.getUserFlag("stopGapGUI") + if sgDetect > 0 then + trigger.action.outText("stopGap: MP activated <" .. sgDetect .. ">, will re-init", 30) + stopGap.turnOff() + stopGap.isMP = true + if stopGap.enabled then + stopGap.turnOn() + end + return + end + end + + -- check if signal for on? or off? if stopGap.turnOn and cfxZones.testZoneFlag(stopGap, stopGap.turnOnFlag, stopGap.triggerMethod, "lastTurnOnFlag") then if not stopGap.enabled then stopGap.turnOn() @@ -299,6 +332,11 @@ function stopGap.createStopGapZone(theZone) if sg then theZone.sgIgnore = false else theZone.sgIgnore = true end end +function stopGap.createStopGapSPZone(theZone) + local sp = cfxZones.getBoolFromZoneProperty(theZone, "stopGapSP", true) + if sp then theZone.spIgnore = false else theZone.spIgnore = true end +end + -- -- Read Config Zone -- @@ -335,6 +373,9 @@ function stopGap.start() stopGap.requiredLibs) then return false end + local sgDetect = trigger.misc.getUserFlag("stopGapGUI") + stopGap.isMP = sgDetect > 0 + local theZone = cfxZones.getZoneByName("stopGapConfig") if not theZone then theZone = cfxZones.createSimpleZone("stopGapConfig") @@ -348,6 +389,13 @@ function stopGap.start() stopGap.stopGapZones[aZone.name] = aZone end + -- collect single-player exclusion zones + local pZones = cfxZones.zonesWithProperty("stopGapSP") + for k, aZone in pairs(pZones) do + stopGap.createStopGapSPZone(aZone) + stopGap.stopGapZones[aZone.name] = aZone + end + -- fill player slots with static objects if stopGap.enabled then stopGap.initGaps() @@ -360,7 +408,10 @@ function stopGap.start() timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) -- say hi! - trigger.action.outText("stopGap v" .. stopGap.version .. " running", 30) + local mp = " (SP - <" .. sgDetect .. ">)" + if sgDetect > 0 then mp = " -- MP GUI Detected (" .. sgDetect .. ")!" end + trigger.action.outText("stopGap v" .. stopGap.version .. " running" .. mp, 30) + return true end diff --git a/server modules/stopGapGUI.lua b/server modules/stopGapGUI.lua index 17262f5..1835328 100644 --- a/server modules/stopGapGUI.lua +++ b/server modules/stopGapGUI.lua @@ -1,5 +1,5 @@ stopGapGUI = {} -stopGapGUI.version = "1.0.0" +stopGapGUI.version = "1.0.1" stopGapGUI.fVal = -300 -- 5 minutes max block -- -- Server Plug-In for StopGap mission script, only required for server @@ -19,4 +19,11 @@ function stopGapGUI.onPlayerTryChangeSlot(playerID, side, slotID) net.send_chat("+++SG: readying group <" .. sgName .. "> for slotting") end +function stopGapGUI.onSimulationStart() + net.dostring_in("server", " trigger.action.setUserFlag(\"stopGapGUI\", 0); ") + if not DCS.isServer() then return end + if not DCS.isMultiplayer() then return end + net.dostring_in("server", " trigger.action.setUserFlag(\"stopGapGUI\", 200); ") -- tells client that MP is active +end + DCS.setUserCallbacks(stopGapGUI) \ No newline at end of file diff --git a/tutorial & demo missions/demo - Caucasus Hangar.miz b/tutorial & demo missions/demo - Caucasus Hangar.miz index b8a70d4..8adbfe6 100644 Binary files a/tutorial & demo missions/demo - Caucasus Hangar.miz and b/tutorial & demo missions/demo - Caucasus Hangar.miz differ diff --git a/tutorial & demo missions/demo - Flag Score.miz b/tutorial & demo missions/demo - Flag Score.miz new file mode 100644 index 0000000..9b9814a Binary files /dev/null and b/tutorial & demo missions/demo - Flag Score.miz differ