diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 9f10aba..9aeda28 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/modules/FARPZones.lua b/modules/FARPZones.lua index ef32af2..42880b9 100644 --- a/modules/FARPZones.lua +++ b/modules/FARPZones.lua @@ -1,17 +1,21 @@ FARPZones = {} -FARPZones.version = "1.0.2" +FARPZones.version = "1.1.0" +FARPZones.verbose = false --[[-- Version History 1.0.0 - Initial Version 1.0.1 - support "none" as defender types - default types for defenders to none 1.0.2 - hiddenRed, hiddenBlue, hiddenGrey + 1.1.0 - config zone + - rFormation attribute added + - verbose flag + - verbose cleanup ("FZ: something happened") --]]-- + FARPZones.requiredLibs = { - "dcsCommon", -- common is of course needed for everything - -- pretty stupid to check for this since we - -- need common to invoke the check, but anyway + "dcsCommon", "cfxZones", -- Zones, of course -- "cfxCommander", -- to make troops do stuff -- "cfxGroundTroops", -- generic when dropping troops @@ -134,25 +138,29 @@ function FARPZones.createFARPFromZone(aZone) aZone, "rPhiHDef", 3) --- trigger.action.outText("*** DEF rPhi are " .. rPhi[1] .. " and " .. rPhi[2], 30) + --trigger.action.outText("*** DEF rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30) -- get r and phi for facilities -- create a new defenderzone for this local r = rPhi[1] local phi = rPhi[2] * 0.0174533 -- 1 degree = 0.0174533 rad local dx = aZone.point.x + r * math.cos(phi) local dz = aZone.point.z + r * math.sin(phi) - theFarp.defZone = cfxZones.createSimpleZone(aZone.name .. "-Def", {x=dx, y = 0, z=dz}, 100) + local formRad = cfxZones.getNumberFromZoneProperty(aZone, "rFormation", 100) + + theFarp.defZone = cfxZones.createSimpleZone(aZone.name .. "-Def", {x=dx, y = 0, z=dz}, formRad) theFarp.defHeading = rPhi[3] + rPhi = {} rPhi = cfxZones.getVectorFromZoneProperty( aZone, "rPhiHRes", - 3) -- optional, will reterurn {0,0} else --- trigger.action.outText("*** RES rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30) + 3) + --trigger.action.outText("*** RES rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30) r = rPhi[1] phi = rPhi[2] * 0.0174533 -- 1 degree = 0.0174533 rad dx = aZone.point.x + r * math.cos(phi) dz = aZone.point.z + r * math.sin(phi) + theFarp.resZone = cfxZones.createSimpleZone(aZone.name .. "-Res", {x=dx, y = 0, z=dz}, 50) theFarp.resHeading = rPhi[3] @@ -307,7 +315,10 @@ function FARPZones.produceVehicles(theFarp) table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback end - -- trigger.action.outText("*** ENTER produce vehicles, will produce " .. theTypes , 30) + if FARPZones.verbose then + trigger.action.outText("*** ENTER produce DEF vehicles, will produce " .. theTypes , 30) + end + local theCoalition = theFarp.owner if theTypes ~= "none" then @@ -364,12 +375,14 @@ function FARPZones.somethingHappened(event) local theUnit = event.initiator local ID = event.id - trigger.action.outText("FZ: something happened", 30) + --trigger.action.outText("FZ: something happened", 30) local aFarp = event.place local zonedFarp = FARPZones.getFARPZoneForFARP(aFarp) - if not zonedFarp then - trigger.action.outText("Hand change, NOT INTERESTING", 30) + if not zonedFarp then + if FARPZones.verbose then + trigger.action.outText("Hand change, NOT INTERESTING", 30) + end return end @@ -380,9 +393,7 @@ function FARPZones.somethingHappened(event) trigger.action.outSound("Quest Snare 3.wav") zonedFarp.owner = newOwner zonedFarp.zone.owner = newOwner - -- better: sound winm and lose to different sides -- update color in map --- FARPZones.drawFarp(zonedFarp) FARPZones.drawFARPCircleInMap(zonedFarp) -- remove all existing resources immediately, @@ -406,6 +417,23 @@ end -- -- Start -- +function FARPZones.readConfig() + local theZone = cfxZones.getZoneByName("farpZonesConfig") + if not theZone then + if FARPZones.verbose then + trigger.action.outText("***frpZ: NO config zone!", 30) + end + return + end + + FARPZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + FARPZones.spinUpDelay = cfxZones.getNumberFromZoneProperty(theZone, "spinUpDelay", 30) + + if FARPZones.verbose then + trigger.action.outText("***frpZ: read config", 30) + end +end function FARPZones.start() @@ -416,7 +444,10 @@ function FARPZones.start() end FARPZones.startingUp = true - + + -- read config zone + FARPZones.readConfig() + -- install callbacks for FARP-relevant events dcsCommon.addEventHandler(FARPZones.somethingHappened, FARPZones.preProcessor, @@ -427,10 +458,11 @@ function FARPZones.start() for k, aZone in pairs(theZones) do local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS FARPZones.addFARPZone(aFARP) -- add to managed zones --- FARPZones.drawFarp(aFARP) FARPZones.drawFARPCircleInMap(aFARP) -- mark in map - FARPZones.produceVehicles(aFARP) -- allocate initial vehicles - --trigger.action.outText("processed FARP " .. aZone.name .. " now owned by " .. aZone.owner, 30) + FARPZones.produceVehicles(aFARP) -- allocate initial vehicles + if FARPZones.verbose then + trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30) + end end FARPZones.startingUp = false diff --git a/modules/RNDFlags.lua b/modules/RNDFlags.lua new file mode 100644 index 0000000..475381e --- /dev/null +++ b/modules/RNDFlags.lua @@ -0,0 +1,350 @@ +rndFlags = {} +rndFlags.version = "1.0.0" +rndFlags.verbose = false +rndFlags.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +--[[ + Random Flags: DML module to select flags at random + and then change them + + Copyright 2022 by Christian Franz and cf/x + + Version History + 1.0.0 - Initial Version + +--]] +rndFlags.rndGen = {} + +function rndFlags.addRNDZone(aZone) + table.insert(rndFlags.rndGen, aZone) +end + +function rndFlags.flagArrayFromString(inString) + if string.len(inString) < 1 then + trigger.action.outText("+++RND: empty flags", 30) + return {} + end + if rndFlags.verbose then + trigger.action.outText("+++RND: processing <" .. inString .. ">", 30) + end + + local flags = {} + local rawElements = dcsCommon.splitString(inString, ",") + + for idx, anElement in pairs(rawElements) do + if dcsCommon.containsString(anElement, "-") then + -- interpret this as a range + local theRange = dcsCommon.splitString(anElement, "-") + local lowerBound = theRange[1] + lowerBound = tonumber(lowerBound) + local upperBound = theRange[2] + upperBound = tonumber(upperBound) + if lowerBound and upperBound then + -- swap if wrong order + if lowerBound > upperBound then + local temp = upperBound + upperBound = lowerBound + lowerBound = temp + end + -- now add add numbers to flags + for f=lowerBound, upperBound do + table.insert(flags, f) + --trigger.action.outText("+++RND: added <" .. f .. "> (range)", 30) + end + else + -- bounds illegal + trigger.action.outText("+++RND: ignored range <" .. anElement .. "> (range)", 30) + end + else + -- single number + f = tonumber(anElement) + if f then + table.insert(flags, f) + --trigger.action.outText("+++RND: added <" .. f .. "> (single)", 30) + else + trigger.action.outText("+++RND: ignored element <" .. anElement .. "> (single)", 30) + end + end + end + if rndFlags.verbose then + trigger.action.outText("+++RND: <" .. #flags .. "> flags total", 30) + end + return flags +end + +-- +-- create rnd gen from zone +-- +function rndFlags.createRNDWithZone(theZone) + local flags = cfxZones.getStringFromZoneProperty(theZone, "flags!", "") + if flags == "" then + -- let's try alternate spelling without "!" + flags = cfxZones.getStringFromZoneProperty(theZone, "flags", "") + end + -- now build the flag array from strings + local theFlags = rndFlags.flagArrayFromString(flags) + theZone.myFlags = theFlags + + + theZone.pollSizeMin, theZone.pollSize = cfxZones.getPositiveRangeFromZoneProperty(theZone, "pollSize", 1) + if rndFlags.verbose then + trigger.action.outText("+++RND: pollSize is <" .. theZone.pollSizeMin .. ", " .. theZone.pollSize .. ">", 30) + end + + + theZone.remove = cfxZones.getBoolFromZoneProperty(theZone, "remove", false) + + -- trigger flag + if cfxZones.hasProperty(theZone, "f?") then + theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none") + end + + if theZone.triggerFlag then + theZone.lastTriggerValue = trigger.misc.getUserFlag(theZone.triggerFlag) -- save last value + end + + theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) + + if not theZone.onStart and not theZone.triggerFlag then + theZone.onStart = true + end + + theZone.method = cfxZones.getStringFromZoneProperty(theZone, "method", "on") + + theZone.reshuffle = cfxZones.getBoolFromZoneProperty(theZone, "reshuffle", false) + if theZone.reshuffle then + -- create a backup copy we can reshuffle from + theZone.flagStore = dcsCommon.copyArray(theFlags) + end + + --theZone.rndPollSize = cfxZones.getBoolFromZoneProperty(theZone, "rndPollSize", false) + + -- done flag + if cfxZones.hasProperty(theZone, "done+1") then + theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "none") + end +end + +function rndFlags.reshuffle(theZone) + if rndFlags.verbose then + trigger.action.outText("+++RND: reshuffling zone " .. theZone.name, 30) + end + theZone.myFlags = dcsCommon.copyArray(theZone.flagStore) +end + +-- +-- fire RND +-- +function rndFlags.pollFlag(theFlag, method) + if rndFlags.verbose then + trigger.action.outText("+++RND: polling flag " .. theFlag .. " with " .. method, 30) + end + + method = method:lower() + local currVal = trigger.misc.getUserFlag(theFlag) + if method == "inc" or method == "f+1" then + trigger.action.setUserFlag(theFlag, currVal + 1) + + elseif method == "dec" or method == "f-1" then + trigger.action.setUserFlag(theFlag, currVal - 1) + + elseif method == "off" or method == "f=0" then + trigger.action.setUserFlag(theFlag, 0) + + elseif method == "flip" or method == "xor" then + if currVal ~= 0 then + trigger.action.setUserFlag(theFlag, 0) + else + trigger.action.setUserFlag(theFlag, 1) + end + + else + if method ~= "on" and method ~= "f=1" then + trigger.action.outText("+++RND: unknown method <" .. method .. "> - using 'on'", 30) + end + -- default: on. + trigger.action.setUserFlag(theFlag, 1) + end + + local newVal = trigger.misc.getUserFlag(theFlag) + if rndFlags.verbose then + trigger.action.outText("+++RND: flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30) + end +end + +function rndFlags.fire(theZone) + -- fire this rnd + -- create a local copy of all flags + if theZone.reshuffle and #theZone.myFlags < 1 then + rndFlags.reshuffle(theZone) + end + + local availableFlags = dcsCommon.copyArray(theZone.myFlags)--{} +-- for idx, aFlag in pairs(theZone.myFlags) do +-- table.insert(availableFlags, aFlag) +-- end + + -- do this pollSize times + local pollSize = theZone.pollSize + local pollSizeMin = theZone.pollSizeMin + + if pollSize ~= pollSizeMin then + -- pick random in range , say 3-7 --> 5 items! + pollSize = (pollSize - pollSizeMin) + 1 -- 7-3 + 1 + pollSize = dcsCommon.smallRandom(pollSize) - 1 --> 0-4 +-- trigger.action.outText("+++RND: RAW pollsize " .. pollSize, 30) + pollSize = pollSize + pollSizeMin +-- trigger.action.outText("+++RND: adj pollsize " .. pollSize, 30) + if pollSize > theZone.pollSize then pollSize = theZone.pollSize end + if pollSize < 1 then pollSize = 1 end + + if rndFlags.verbose then + trigger.action.outText("+++RND: RND " .. theZone.name .. " range " .. pollSizeMin .. "-" .. theZone.pollSize .. ": selected " .. pollSize, 30) + end + end + + if #availableFlags < 1 then + if rndFlags.verbose then + trigger.action.outText("+++RND: RND " .. theZone.name .. " ran out of flags. aborting fire", 30) + end + + if theZone.doneFlag then + local currVal = trigger.misc.getUserFlag(theZone.doneFlag) + trigger.action.setUserFlag(theZone.doneFlag, currVal + 1) + end + + return + end + + if rndFlags.verbose then + trigger.action.outText("+++RND: firing RND " .. theZone.name .. " with pollsize " .. pollSize .. " on " .. #availableFlags .. " set size", 30) + end + + for i=1, pollSize do + -- check there are still flags left + if #availableFlags < 1 then + trigger.action.outText("+++RND: no flags left in " .. theZone.name .. " in index " .. i, 30) + theZone.myFlags = {} + if theZone.reshuffle then + rndFlags.reshuffle(theZone) + end + return + end + + -- select a flag, enforce uniqueness + local theFlagIndex = dcsCommon.smallRandom(#availableFlags) + + -- poll this flag and remove from available + local theFlag = table.remove(availableFlags,theFlagIndex) + + rndFlags.pollFlag(theFlag, theZone.method) + + end + + -- remove if requested + if theZone.remove then + theZone.myFlags = availableFlags + end +end + +-- +-- update +-- +function rndFlags.update() + -- call me in a second to poll triggers + timer.scheduleFunction(rndFlags.update, {}, timer.getTime() + 1) + + for idx, aZone in pairs(rndFlags.rndGen) do + if aZone.triggerFlag then + local currTriggerVal = trigger.misc.getUserFlag(aZone.triggerFlag) + if currTriggerVal ~= aZone.lastTriggerValue + then + if rndFlags.verbose then + trigger.action.outText("+++RND: triggering " .. aZone.name, 30) + end + rndFlags.fire(aZone) + aZone.lastTriggerValue = currTriggerVal + end + + end + end +end + +-- +-- start cycle: force all onStart to fire +-- +function rndFlags.startCycle() + for idx, theZone in pairs(rndFlags.rndGen) do + if theZone.onStart then + trigger.action.outText("+++RND: starting " .. theZone.name, 30) + rndFlags.fire(theZone) + end + end +end + + +-- +-- start module and read config +-- +function rndFlags.readConfigZone() + -- note: must match exactly!!!! + local theZone = cfxZones.getZoneByName("rndFlagsConfig") + if not theZone then + if rndFlags.verbose then + trigger.action.outText("***RND: NO config zone!", 30) + end + return + end + + rndFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + if rndFlags.verbose then + trigger.action.outText("***RND: read config", 30) + end +end + +function rndFlags.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("RNDFlags requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Random Flags", + rndFlags.requiredLibs) then + return false + end + + -- read config + rndFlags.readConfigZone() + + -- process RND Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("RND") + + -- now create an rnd gen for each one and add them + -- to our watchlist + for k, aZone in pairs(attrZones) do + rndFlags.createRNDWithZone(aZone) -- process attribute and add to zone + rndFlags.addRNDZone(aZone) -- remember it so we can smoke it + end + + -- start cycle + timer.scheduleFunction(rndFlags.startCycle, {}, timer.getTime() + 0.25) + + -- start update + timer.scheduleFunction(rndFlags.update, {}, timer.getTime() + 1) + + trigger.action.outText("cfx random Flags v" .. rndFlags.version .. " started.", 30) + return true +end + +-- let's go! +if not rndFlags.start() then + trigger.action.outText("cf/x RND Flags aborted: missing libraries", 30) + rndFlags = nil +end + +--[[ +pulser / repeat until +--]] \ No newline at end of file diff --git a/modules/cargoSuper.lua b/modules/cargoSuper.lua index ba33560..a3eec5c 100644 --- a/modules/cargoSuper.lua +++ b/modules/cargoSuper.lua @@ -1,5 +1,5 @@ cargoSuper = {} -cargoSuper.version = "1.1.0" +cargoSuper.version = "1.1.1" --[[-- version history 1.0.0 - initial version @@ -13,6 +13,7 @@ version history - getAllCategoriesFor alias for getAllCargos - getManifestForCategory alias for getManifestFor - removeAllMassFor() + 1.1.1 - deleteMassObject corrected index bug CargoSuper manages weigth for a logical named unit. Weight can be added to arbitrary categories like 'passengers', 'cargo' or "whatever". In order @@ -68,7 +69,7 @@ function cargoSuper.deleteMassObject(massObject) else theName = massObject.name end - cargoSuper.massObjects[massName] = nil + cargoSuper.massObjects[theName] = nil -- 1.1.1 corrected to theName from massName end function cargoSuper.addMassObjectTo(name, category, theMassObject) diff --git a/modules/cfxNDB.lua b/modules/cfxNDB.lua index 637904d..d5eb0d2 100644 --- a/modules/cfxNDB.lua +++ b/modules/cfxNDB.lua @@ -1,5 +1,5 @@ cfxNDB = {} -cfxNDB.version = "1.0.0" +cfxNDB.version = "1.1.0" --[[-- cfxNDB: @@ -12,10 +12,20 @@ cfxNDB.version = "1.0.0" correctly if it's longer than the module's refresh and an even multiple of module's refresh, else it will be refreshed at the next module update cycle + + VERSION HISTORY + 1.0.0 - initial version + 1.1.0 - on? flag + - off? flag + - ups at 1, decoupled update from refresh + - paused flag, paused handling + - startNDB() can accept string + - stopNDB() can accept string + --]]-- cfxNDB.verbose = false -cfxNDB.ups = 10 -- every 10 seconds +cfxNDB.ups = 1 -- once every 1 second cfxNDB.requiredLibs = { "dcsCommon", "cfxZones", @@ -30,6 +40,18 @@ cfxNDB.ndbs = {} -- all ndbs -- function cfxNDB.startNDB(theNDB) + if type(theNDB) == "string" then + theNDB = cfxZones.getZoneByName(theNDB) + end + + if not theNDB.freq then + -- this zone is not an NDB. Exit + if cfxNDB.verbose then + trigger.action.outText("+++ndb: start() -- " .. theNDB.name .. " is not a cfxNDB.", 30) + end + return + end + theNDB.ndbRefreshTime = timer.getTime() + theNDB.ndbRefresh -- only used in linkedUnit, but set up anyway -- generate new ID theNDB.ndbID = dcsCommon.uuid("ndb") @@ -47,10 +69,31 @@ function cfxNDB.startNDB(theNDB) end trigger.action.outText("+++ndb: started " .. theNDB.name .. dsc .. " at " .. theNDB.freq/1000000 .. "mod " .. modulation .. " with w=" .. theNDB.power .. " s=<" .. fileName .. ">", 30) end + theNDB.paused = false + + if cfxNDB.verbose then + trigger.action.outText("+++ndb: " .. theNDB.name .. " started", 30) + end end function cfxNDB.stopNDB(theNDB) + if type(theNDB) == "string" then + theNDB = cfxZones.getZoneByName(theNDB) + end + + if not theNDB.freq then + -- this zone is not an NDB. Exit + if cfxNDB.verbose then + trigger.action.outText("+++ndb: stop() -- " .. theNDB.name .. " is not a cfxNDB.", 30) + end + return + end + trigger.action.stopRadioTransmission(theNDB.ndbID) + theNDB.paused = true + if cfxNDB.verbose then + trigger.action.outText("+++ndb: " .. theNDB.name .. " stopped", 30) + end end function cfxNDB.createNDBWithZone(theZone) @@ -65,8 +108,31 @@ function cfxNDB.createNDBWithZone(theZone) -- when LARGER than module's refresh. theZone.ndbRefresh = cfxZones.getNumberFromZoneProperty(theZone, "ndbRefresh", cfxNDB.refresh) -- only used if linked theZone.ndbRefreshTime = timer.getTime() + theZone.ndbRefresh -- only used with linkedUnit, but set up nonetheless + + -- paused + theZone.paused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) + + -- on/offf query flags + if cfxZones.hasProperty(theZone, "on?") then + theZone.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "none") + end + + if theZone.onFlag then + theZone.onFlagVal = trigger.misc.getUserFlag(theZone.onFlag) -- save last value + end + + if cfxZones.hasProperty(theZone, "off?") then + theZone.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "none") + end + + if theZone.offFlag then + theZone.offFlagVal = trigger.misc.getUserFlag(theZone.offFlag) -- save last value + end + -- start it - cfxNDB.startNDB(theZone) + if not theZone.paused then + cfxNDB.startNDB(theZone) + end -- add it to my watchlist table.insert(cfxNDB.ndbs, theZone) @@ -84,11 +150,32 @@ function cfxNDB.update() -- moving with the linked unit if theNDB.linkedUnit then -- yupp, need to update - if now > theNDB.ndbRefreshTime then - cfxNDB.stopNDB(theNDB) - cfxNDB.startNDB(theNDB) + if (not theNDB.paused) and + (now > theNDB.ndbRefreshTime) then + cfxNDB.stopNDB(theNDB) -- also pauses + cfxNDB.startNDB(theNDB) -- turns off pause end end + + -- now check triggers to start/stop + if theNDB.onFlagVal then + -- see if this changed + local currTriggerVal = trigger.misc.getUserFlag(theNDB.onFlag) + if currTriggerVal ~= theNDB.onFlagVal then + -- yupp, trigger start + cfxNDB.startNDB(theNDB) + theNDB.onFlagVal = currTriggerVal + end + end + + if theNDB.offFlagVal then + local currTriggerVal = trigger.misc.getUserFlag(theNDB.offFlag) + if currTriggerVal ~= theNDB.offFlagVal then + -- yupp, trigger start + cfxNDB.stopNDB(theNDB) + theNDB.offFlagVal = currTriggerVal + end + end end end @@ -137,4 +224,4 @@ end if not cfxNDB.start() then trigger.action.outText("cf/x NDB aborted: missing libraries", 30) cfxNDB = nil -end \ No newline at end of file +end diff --git a/modules/cfxObjectSpawnZones.lua b/modules/cfxObjectSpawnZones.lua index e6cddb7..b24f8ac 100644 --- a/modules/cfxObjectSpawnZones.lua +++ b/modules/cfxObjectSpawnZones.lua @@ -1,5 +1,5 @@ cfxObjectSpawnZones = {} -cfxObjectSpawnZones.version = "1.1.3" +cfxObjectSpawnZones.version = "1.1.4" cfxObjectSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -22,6 +22,7 @@ cfxObjectSpawnZones.ups = 1 -- 1.1.2 - autoRemove option re-installed -- - added possibility to autoUnlink -- 1.1.3 - ME-triggered flag via f? and triggerFlag +-- 1.1.4 - activate?, pause? attributes -- Object spawn zones have the following major uses: -- - dynamically spawn cargo @@ -84,6 +85,16 @@ function cfxObjectSpawnZones.createSpawner(inZone) theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) end + if cfxZones.hasProperty(inZone, "activate?") then + theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none") + theSpawner.lastActivateValue = trigger.misc.getUserFlag(theSpawner.activateFlag) + end + + if cfxZones.hasProperty(inZone, "pause?") then + theSpawner.pauseFlag = cfxZones.getStringFromZoneProperty(inZone, "pause?", "none") + theSpawner.lastPauseValue = trigger.misc.getUserFlag(theSpawner.pauseFlag) + end + --theSpawner.types = cfxZones.getZoneProperty(inZone, "types") theSpawner.types = cfxZones.getStringFromZoneProperty(inZone, "types", "White_Tyre") local n = cfxZones.getNumberFromZoneProperty(inZone, "count", 1) -- DO NOT CONFUSE WITH OWN PROPERTY COUNT for unique names!!! @@ -410,6 +421,15 @@ function cfxObjectSpawnZones.update() local needsSpawn = cfxObjectSpawnZones.needsSpawning(spawner) -- check if perhaps our watchtrigger causes spawn + if spawner.pauseFlag then + local currTriggerVal = trigger.misc.getUserFlag(spawner.pauseFlag) + if currTriggerVal ~= spawner.lastPauseValue then + spawner.paused = true + needsSpawn = false + spawner.lastPauseValue = currTriggerVal + end + end + if spawner.triggerFlag then local currTriggerVal = trigger.misc.getUserFlag(spawner.triggerFlag) if currTriggerVal ~= spawner.lastTriggerValue then @@ -418,6 +438,17 @@ function cfxObjectSpawnZones.update() end end + if spawner.activateFlag then + local currTriggerVal = trigger.misc.getUserFlag(spawner.activateFlag) + if currTriggerVal ~= spawner.lastActivateValue then + spawner.paused = false + spawner.lastActivateValue = currTriggerVal + end + end + + + + if needsSpawn then cfxObjectSpawnZones.spawnWithSpawner(spawner) if spawner.maxSpawns > 0 then diff --git a/modules/cfxSmokeZones.lua b/modules/cfxSmokeZones.lua index 0e38869..f3800e1 100644 --- a/modules/cfxSmokeZones.lua +++ b/modules/cfxSmokeZones.lua @@ -1,5 +1,5 @@ cfxSmokeZone = {} -cfxSmokeZone.version = "1.0.2" +cfxSmokeZone.version = "1.0.3" cfxSmokeZone.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -9,7 +9,10 @@ cfxSmokeZone.requiredLibs = { 1.0.0 - initial version 1.0.1 - added removeSmokeZone 1.0.2 - added altitude - + 1.0.3 - added paused attribute + - added f? attribute --> onFlag + - broke out startSmoke + SMOKE ZONES *** EXTENDS ZONES *** keeps 'eternal' smoke up for any zone that has the 'smoke' attribute @@ -34,27 +37,61 @@ function cfxSmokeZone.processSmokeZone(aZone) aZone.smokeColor = theColor aZone.smokeAlt = cfxZones.getNumberFromZoneProperty(aZone, "altitude", 1) + + -- paused + aZone.paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false) + + -- f? query flags + if cfxZones.hasProperty(aZone, "f?") then + aZone.onFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none") + end + + if aZone.onFlag then + aZone.onFlagVal = trigger.misc.getUserFlag(aZone.onFlag) -- save last value + end end function cfxSmokeZone.addSmokeZone(aZone) table.insert(cfxSmokeZone.smokeZones, aZone) end -function cfxSmokeZone.addSmokeZoneWithColor(aZone, aColor, anAltitude) +function cfxSmokeZone.addSmokeZoneWithColor(aZone, aColor, anAltitude, paused, onFlag) if not aColor then aColor = 0 end -- default green if not anAltitude then anAltitude = 5 end if not aZone then return end + if not paused then paused = false end + aZone.smokeColor = aColor aZone.smokeAlt = anAltitude + aZone.paused = paused + + if onFlag then + aZone.onFlag = onFlag + aZone.onFlagVal = trigger.misc.getUserFlag(onFlag) + end + cfxSmokeZone.addSmokeZone(aZone) -- add to update loop - cfxZones.markZoneWithSmoke(aZone, 0, 0, aZone.smokeColor, aZone.smokeAlt) -- smoke on! + if not paused then + cfxSmokeZone.startSmoke(aZone) + end + end -function cfxSmokeZone.removeSmokeZone(aZone) - if not aZone then return end +function cfxSmokeZone.startSmoke(aZone) if type(aZone) == "string" then aZone = cfxZones.getZoneByName(aZone) end + if not aZone then return end + if not aZone.smokeColor then return end + aZone.paused = false + cfxZones.markZoneWithSmoke(aZone, 0, 0, aZone.smokeColor, aZone.smokeAlt) +end + +function cfxSmokeZone.removeSmokeZone(aZone) + if type(aZone) == "string" then + aZone = cfxZones.getZoneByName(aZone) + end + if not aZone then return end -- now create new table local filtered = {} @@ -73,8 +110,25 @@ function cfxSmokeZone.update() -- re-smoke all zones after delay for idx, aZone in pairs(cfxSmokeZone.smokeZones) do - if aZone.smokeColor then - cfxZones.markZoneWithSmoke(aZone, 0, 0, aZone.smokeColor, aZone.smokeAlt) + if not aZone.paused and aZone.smokeColor then + cfxSmokeZone.startSmoke(aZone) + + end + end +end + + +function cfxSmokeZone.checkFlags() + timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) -- every second + for idx, aZone in pairs(cfxSmokeZone.smokeZones) do + if aZone.paused and aZone.onFlagVal then + -- see if this changed + local currTriggerVal = trigger.misc.getUserFlag(aZone.onFlag) + if currTriggerVal ~= aZone.onFlagVal then + -- yupp, trigger start + cfxSmokeZone.startSmoke(aZone) + aZone.onFlagVal = currTriggerVal + end end end end @@ -89,15 +143,18 @@ function cfxSmokeZone.start() -- collect all spawn zones local attrZones = cfxZones.getZonesWithAttributeNamed("smoke") - -- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not - -- paused + -- now create a smoker for all, add them to updater, + -- smoke all that aren't paused for k, aZone in pairs(attrZones) do cfxSmokeZone.processSmokeZone(aZone) -- process attribute and add to zone cfxSmokeZone.addSmokeZone(aZone) -- remember it so we can smoke it end -- start update loop - cfxSmokeZone.update() + cfxSmokeZone.update() -- also starts all unpaused + + -- start check loop in one second + timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) -- say hi trigger.action.outText("cfx Smoke Zones v" .. cfxSmokeZone.version .. " started.", 30) diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index 428c815..620a707 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.5.1" +cfxSpawnZones.version = "1.5.2" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -51,6 +51,7 @@ cfxSpawnZones.verbose = false -- - spawnWithSpawner made string compatible -- 1.5.1 - relaxed baseName and default to dcsCommon.uuid() -- - verbose +-- 1.5.2 - activate?, pause? flag -- -- new version requires cfxGroundTroops, where they are -- @@ -139,6 +140,16 @@ function cfxSpawnZones.createSpawner(inZone) theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) end + if cfxZones.hasProperty(inZone, "activate?") then + theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none") + theSpawner.lastActivateValue = trigger.misc.getUserFlag(theSpawner.activateFlag) + end + + if cfxZones.hasProperty(inZone, "pause?") then + theSpawner.pauseFlag = cfxZones.getStringFromZoneProperty(inZone, "pause?", "none") + theSpawner.lastPauseValue = trigger.misc.getUserFlag(theSpawner.pauseFlag) + end + theSpawner.types = cfxZones.getZoneProperty(inZone, "types") --theSpawner.owner = cfxZones.getCoalitionFromZoneProperty(inZone, "owner", 0) -- synthesize types * typeMult @@ -407,7 +418,16 @@ function cfxSpawnZones.update() -- is master zone still alinged with me? needsSpawn = needsSpawn and cfxSpawnZones.verifySpawnOwnership(spawner) - -- check if perhaps our watchtrigger causes spawn + -- check if perhaps our watchtriggers causes spawn + if spawner.pauseFlag then + local currTriggerVal = trigger.misc.getUserFlag(spawner.pauseFlag) + if currTriggerVal ~= spawner.lastPauseValue then + spawner.paused = true + needsSpawn = false + spawner.lastPauseValue = currTriggerVal + end + end + if spawner.triggerFlag then local currTriggerVal = trigger.misc.getUserFlag(spawner.triggerFlag) if currTriggerVal ~= spawner.lastTriggerValue then @@ -415,6 +435,16 @@ function cfxSpawnZones.update() spawner.lastTriggerValue = currTriggerVal end end + + if spawner.activateFlag then + local currTriggerVal = trigger.misc.getUserFlag(spawner.activateFlag) + if currTriggerVal ~= spawner.lastActivateValue then + spawner.paused = false + spawner.lastActivateValue = currTriggerVal + end + end + + -- if we get here, and needsSpawn is still set, we go ahead and spawn if needsSpawn then diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 093ef6f..291b966 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -6,7 +6,7 @@ -- cfxZones = {} -cfxZones.version = "2.5.1" +cfxZones.version = "2.5.2" --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -43,6 +43,8 @@ cfxZones.version = "2.5.1" - 2.4.12 - getStringFromZoneProperty - 2.5.0 - harden getZoneProperty and all getPropertyXXXX - 2.5.1 - markZoneWithSmoke supports alt attribute + - 2.5.2 - getPoint also writes through to zone itself for optimization + - new method getPositiveRangeFromZoneProperty(theZone, theProperty, default) --]]-- cfxZones.verbose = true @@ -1100,6 +1102,45 @@ function cfxZones.getMinMaxFromZoneProperty(theZone, theProperty) end +function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default) + -- reads property as string, and interprets as range 'a-b'. + -- if not a range but single number, returns both for upper and lower + --trigger.action.outText("***Zne: enter with <" .. theZone.name .. ">: range for property <" .. theProperty .. ">!", 30) + if not default then default = 0 end + local lowerBound = default + local upperBound = default + + local rangeString = cfxZones.getStringFromZoneProperty(theZone, theProperty, "") + if dcsCommon.containsString(rangeString, "-") then + local theRange = dcsCommon.splitString(rangeString, "-") + lowerBound = theRange[1] + lowerBound = tonumber(lowerBound) + upperBound = theRange[2] + upperBound = tonumber(upperBound) + if lowerBound and upperBound then + -- swap if wrong order + if lowerBound > upperBound then + local temp = upperBound + upperBound = lowerBound + lowerBound = temp + end +-- if rndFlags.verbose then +-- trigger.action.outText("+++Zne: detected range <" .. lowerBound .. ", " .. upperBound .. ">", 30) +-- end + else + -- bounds illegal + trigger.action.outText("+++Zne: illegal range <" .. rangeString .. ">, using " .. default .. "-" .. default, 30) + lowerBound = default + upperBound = default + end + else + upperBound = cfxZones.getNumberFromZoneProperty(theZone, theProperty, default) -- between pulses + lowerBound = upperBound + end +-- trigger.action.outText("+++Zne: returning <" .. lowerBound .. ", " .. upperBound .. ">", 30) + return lowerBound, upperBound +end + function cfxZones.hasProperty(theZone, theProperty) return cfxZones.getZoneProperty(theZone, theProperty) ~= nil end @@ -1223,7 +1264,7 @@ end -- -- requires that readFromDCS has been done -- -function cfxZones.getPoint(aZone) -- always works, wven linked, point can be reused +function cfxZones.getPoint(aZone) -- always works, even linked, point can be reused if aZone.linkedUnit then local theUnit = aZone.linkedUnit -- has a link. is link existing? @@ -1237,6 +1278,8 @@ function cfxZones.getPoint(aZone) -- always works, wven linked, point can be reu thePos.x = aZone.point.x thePos.y = 0 -- aZone.y thePos.z = aZone.point.z + -- since we are at it, update the zone as well + aZone.point = thePos return thePos end diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index ae8f686..75a830b 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.5.1" +dcsCommon.version = "2.5.2" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -56,6 +56,8 @@ dcsCommon.version = "2.5.1" 2.5.0 - "Line" formation with one unit places unit at center 2.5.1 - vNorm(a) 2.5.1 - added SA-18 Igla manpad to unitIsInfantry() + 2.5.2 - added copyArray method + - corrected heading in createStaticObjectData --]]-- -- dcsCommon is a library of common lua functions @@ -825,7 +827,14 @@ dcsCommon.version = "2.5.1" return copy end - + function dcsCommon.copyArray(inArray) + -- warning: this is a ref copy! + local theCopy = {} + for idx, element in pairs(inArray) do + table.insert(theCopy, element) + end + return theCopy + end -- -- -- S P A W N I N G @@ -1355,7 +1364,7 @@ dcsCommon.version = "2.5.1" -- now loop and create a unit for each table local num = 1 for key, theType in pairs(theUnitTypes) do --- trigger.action.outText("creating unit " .. name .. "-" .. num, 30) + -- trigger.action.outText("+++dcsC: creating unit " .. name .. "-" .. num .. ": " .. theType, 30) local aUnit = dcsCommon.createGroundUnitData(name .. "-"..num, theType, false) dcsCommon.addUnitToGroupData(aUnit, theNewGroup, 0, 0) num = num + 1 @@ -1478,7 +1487,7 @@ dcsCommon.version = "2.5.1" if not cargo then cargo = false end objType = dcsCommon.trim(objType) - staticObj.heading = 0 + staticObj.heading = heading -- staticObj.groupId = 0 -- staticObj.shape_name = shape -- e.g. H-Windsock_RW staticObj.type = objType -- e.g. Windsock diff --git a/modules/pulseFlags.lua b/modules/pulseFlags.lua new file mode 100644 index 0000000..5fc7962 --- /dev/null +++ b/modules/pulseFlags.lua @@ -0,0 +1,268 @@ +pulseFlags = {} +pulseFlags.version = "1.0.0" +pulseFlags.verbose = false +pulseFlags.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +--[[-- + Pulse Flags: DML module to regularly change a flag + + Copyright 2022 by Christian Franz and cf/x + + Version History + - 1.0.0 Initial version + +--]]-- + +pulseFlags.pulses = {} + +function pulseFlags.addPulse(aZone) + table.insert(pulseFlags.pulses, aZone) +end + +-- +-- create a pulse +-- + +function pulseFlags.createPulseWithZone(theZone) + theZone.flag = cfxZones.getNumberFromZoneProperty(theZone, "flag!", -1) -- the flag to pulse + + -- time can be number, or number-number range + theZone.minTime, theZone.time = cfxZones.getPositiveRangeFromZoneProperty(theZone, "time", 1) + + trigger.action.outText("***PulF: zone <" .. theZone.name .. "> time is <".. theZone.minTime ..", " .. theZone.time .. "!", 30) + + theZone.pulses = cfxZones.getNumberFromZoneProperty(theZone, "pulses", -1) + theZone.pulsesLeft = 0 -- will start new cycle + + -- trigger flags + if cfxZones.hasProperty(theZone, "activate?") then + theZone.activateFlag = cfxZones.getStringFromZoneProperty(theZone, "activate?", "none") + theZone.lastActivateValue = trigger.misc.getUserFlag(theZone.activateFlag) -- save last value + end + + if cfxZones.hasProperty(theZone, "pause?") then + theZone.pauseFlag = cfxZones.getStringFromZoneProperty(theZone, "pause?", "none") + theZone.lastPauseValue = trigger.misc.getUserFlag(theZone.pauseFlag) -- save last value + end + + theZone.paused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) + + theZone.method = cfxZones.getStringFromZoneProperty(theZone, "method", "flip") + + -- done flag + if cfxZones.hasProperty(theZone, "done+1") then + theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "none") + end + + theZone.pulsing = false -- not running + +end + +-- +-- update +-- +function pulseFlags.pollFlag(theFlag, method) + if pulseFlags.verbose then + trigger.action.outText("+++PulF: polling flag " .. theFlag .. " with " .. method, 30) + end + + method = method:lower() + local currVal = trigger.misc.getUserFlag(theFlag) + if method == "inc" or method == "f+1" then + trigger.action.setUserFlag(theFlag, currVal + 1) + + elseif method == "dec" or method == "f-1" then + trigger.action.setUserFlag(theFlag, currVal - 1) + + elseif method == "off" or method == "f=0" then + trigger.action.setUserFlag(theFlag, 0) + + elseif method == "flip" or method == "xor" then + if currVal ~= 0 then + trigger.action.setUserFlag(theFlag, 0) + else + trigger.action.setUserFlag(theFlag, 1) + end + + else + if method ~= "on" and method ~= "f=1" then + trigger.action.outText("+++PulF: unknown method <" .. method .. "> - using 'on'", 30) + end + -- default: on. + trigger.action.setUserFlag(theFlag, 1) + end + + local newVal = trigger.misc.getUserFlag(theFlag) + if pulseFlags.verbose then + trigger.action.outText("+++PulF: flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30) + end +end + + +function pulseFlags.doPulse(args) + local theZone = args[1] + -- check if we have been paused. if so, simply + -- exit with no new schedule + if theZone.paused then + theZone.pulsing = false + return + end + + -- do a poll on flags + pulseFlags.pollFlag(theZone.flag, theZone.method) + + -- decrease count + if theZone.pulses > 0 then + -- only do this if ending + theZone.pulsesLeft = theZone.pulsesLeft - 1 + + -- see if we are done + if theZone.pulsesLeft < 1 then + -- increment done flag if set + if theZone.doneFlag then + local currVal = trigger.misc.getUserFlag(theZone.doneFlag) + trigger.action.setUserFlag(theZone.doneFlag, currVal + 1) + end + if pulseFlags.verbose then + trigger.action.outText("***PulF: pulse <" .. theZone.name .. "> ended!", 30) + end + theZone.pulsing = false + theZone.paused = true + return + end + end + + -- if we get here, we'll do another one soon + -- refresh pulse + local delay = theZone.time + if theZone.minTime > 0 and theZone.minTime < delay then + -- we want a randomized from time from minTime .. delay + local varPart = delay - theZone.minTime + 1 + varPart = dcsCommon.smallRandom(varPart) - 1 + delay = theZone.minTime + varPart + end + + --trigger.action.outText("***PulF: pulse <" .. theZone.name .. "> scheduled in ".. delay .."!", 30) + + -- schedule in delay time + timer.scheduleFunction(pulseFlags.doPulse, args, timer.getTime() + delay) + if pulseFlags.verbose then + trigger.action.outText("+++PulF: pulse <" .. theZone.name .. "> rescheduled in " .. delay, 30) + end +end + + +-- start new pulse, will reset +function pulseFlags.startNewPulse(theZone) + theZone.pulsesLeft = theZone.pulses + local args = {theZone} + theZone.pulsing = true + if pulseFlags.verbose then + trigger.action.outText("+++PulF: starting pulse <" .. theZone.name .. ">", 30) + end + pulseFlags.doPulse(args) +end + +function pulseFlags.update() + -- call me in a second to poll triggers + timer.scheduleFunction(pulseFlags.update, {}, timer.getTime() + 1) + + for idx, aZone in pairs(pulseFlags.pulses) do + -- see if pulse is running + if aZone.pulsing then + -- this zone has a pulse and has scheduled + -- a new pulse, nothing to do + + else + -- this zone has not scheduled a new pulse + -- let's see why + if aZone.paused then + -- ok, zone is paused. all clear + else + -- zone isn't paused. we need to start the zone + pulseFlags.startNewPulse(aZone) + end + end + + -- see if we got a pause or activate command + if aZone.activateFlag then + local currTriggerVal = trigger.misc.getUserFlag(aZone.activateFlag) + if currTriggerVal ~= aZone.lastActivateValue + then + trigger.action.outText("+++PulF: activating <" .. aZone.name .. ">", 30) + aZone.lastActivateValue = currTriggerVal + theZone.paused = false -- will start anew + end + end + + if aZone.pauseFlag then + local currTriggerVal = trigger.misc.getUserFlag(aZone.pauseFlag) + if currTriggerVal ~= aZone.lastPauseValue + then + trigger.action.outText("+++PulF: pausing <" .. aZone.name .. ">", 30) + aZone.lastPauseValue = currTriggerVal + theZone.paused = true -- will start anew + end + end + end +end + +-- +-- start module and read config +-- +function pulseFlags.readConfigZone() + -- note: must match exactly!!!! + local theZone = cfxZones.getZoneByName("pulseFlagsConfig") + if not theZone then + if pulseFlags.verbose then + trigger.action.outText("+++PulF: NO config zone!", 30) + end + return + end + + pulseFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + if pulseFlags.verbose then + trigger.action.outText("+++PulF: read config", 30) + end +end + +function pulseFlags.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("PulseFlags requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Pulse Flags", + pulseFlags.requiredLibs) then + return false + end + + -- read config + pulseFlags.readConfigZone() + + -- process RND Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("pulse") + + -- now create a pulse gen for each one and add them + -- to our watchlist + for k, aZone in pairs(attrZones) do + pulseFlags.createPulseWithZone(aZone) -- process attribute and add to zone + pulseFlags.addPulse(aZone) -- remember it so we can pulse it + end + + -- start update in 1 second + --pulseFlags.update() + timer.scheduleFunction(pulseFlags.update, {}, timer.getTime() + 1) + + trigger.action.outText("cfx Pulse Flags v" .. pulseFlags.version .. " started.", 30) + return true +end + +-- let's go! +if not pulseFlags.start() then + trigger.action.outText("cf/x Pulse Flags aborted: missing libraries", 30) + pulseFlags = nil +end \ No newline at end of file diff --git a/tutorial & demo missions/demo - ADF and NDB fun.miz b/tutorial & demo missions/demo - ADF and NDB fun.miz index 8a57e64..f3104c1 100644 Binary files a/tutorial & demo missions/demo - ADF and NDB fun.miz and b/tutorial & demo missions/demo - ADF and NDB fun.miz differ diff --git a/tutorial & demo missions/demo - FARP and away.miz b/tutorial & demo missions/demo - FARP and away.miz new file mode 100644 index 0000000..043ff27 Binary files /dev/null and b/tutorial & demo missions/demo - FARP and away.miz differ diff --git a/tutorial & demo missions/demo - Pulsing Fun.miz b/tutorial & demo missions/demo - Pulsing Fun.miz new file mode 100644 index 0000000..2b7ecf9 Binary files /dev/null and b/tutorial & demo missions/demo - Pulsing Fun.miz differ diff --git a/tutorial & demo missions/demo - Random Death.miz b/tutorial & demo missions/demo - Random Death.miz new file mode 100644 index 0000000..4dd82fc Binary files /dev/null and b/tutorial & demo missions/demo - Random Death.miz differ diff --git a/tutorial & demo missions/demo - Random Glory.miz b/tutorial & demo missions/demo - Random Glory.miz new file mode 100644 index 0000000..8d0e34f Binary files /dev/null and b/tutorial & demo missions/demo - Random Glory.miz differ