diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 0574cc6..f36ed65 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 973547a..461433a 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index fcaa3ea..dfca4a5 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -5,7 +5,7 @@ -- Copyright (c) 2021, 2022 by Christian Franz and cf/x AG -- cfxZones = {} -cfxZones.version = "2.7.9" +cfxZones.version = "2.8.1" --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -80,7 +80,8 @@ cfxZones.version = "2.7.9" - unpulse - 2.7.9 - getFlagValue QoL for - setFlagValue QoL for - +- 2.8.0 - new allGroupNamesInZone() +- 2.8.1 - new zonesLinkedToUnit() --]]-- cfxZones.verbose = false @@ -599,6 +600,22 @@ function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be return inZones end +function cfxZones.allGroupNamesInZone(theZone, categ) -- categ is optional, must be code + -- warning: does not check for exiting! + --trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30) + local inZones = {} + local coals = {0, 1, 2} -- all coalitions + for idx, coa in pairs(coals) do + local allGroups = coalition.getGroups(coa, categ) + for key, group in pairs(allGroups) do -- iterate all groups + if cfxZones.isGroupPartiallyInZone(group, theZone) then + table.insert(inZones, group:getName()) + end + end + end + return inZones +end + function cfxZones.allStaticsInZone(theZone) -- categ is optional, must be code -- warning: does not check for exiting! local inZones = {} @@ -1928,6 +1945,17 @@ function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really theZone.dy = dy end +function cfxZones.zonesLinkedToUnit(theUnit) -- returns all zones linked to this unit + if not theUnit then return {} end + local linkedZones = {} + for idx, theZone in pairs (cfxZones.zones) do + if theZone.linkedUnit == theUnit then + table.insert(linkedZones, theZone) + end + end + return linkedZones +end + function cfxZones.updateMovingZones() cfxZones.updateSchedule = timer.scheduleFunction(cfxZones.updateMovingZones, {}, timer.getTime() + 1/cfxZones.ups) -- simply scan all cfx zones for the linkedUnit property and if there diff --git a/modules/groupTrackers.lua b/modules/groupTrackers.lua index 12f87b7..e082718 100644 --- a/modules/groupTrackers.lua +++ b/modules/groupTrackers.lua @@ -1,5 +1,5 @@ groupTracker = {} -groupTracker.version = "1.1.2" +groupTracker.version = "1.1.3" groupTracker.verbose = false groupTracker.ups = 1 groupTracker.requiredLibs = { @@ -17,7 +17,9 @@ groupTracker.trackers = {} 1.1.1 - corrected clone zone reference bug 1.1.2 - corrected naming (removed bang from flags), deprecated old - more zone-local verbosity - + 1.1.3 - spellings + - addGroupToTrackerNamed bug removed accessing tracker + - new removeGroupNamedFromTrackerNamed() --]]-- function groupTracker.addTracker(theZone) @@ -56,7 +58,7 @@ function groupTracker.addGroupToTracker(theGroup, theTracker) end if groupTracker.verbose or theTracker.verbose then - trigger.action.outText("+++gTrk: will add group <" .. theGroup:getName() .. "> to tracker " .. theTracker.name, 30) + trigger.action.outText("+++gTrk: adding group <" .. theGroup:getName() .. "> to tracker " .. theTracker.name, 30) end -- we have the tracker, add the group @@ -86,14 +88,59 @@ function groupTracker.addGroupToTrackerNamed(theGroup, trackerName) end if not theGroup:isExist() then - trigger.action.outText("+++gTrk: group does not exist in when adding to tracker <" .. trackerName .. ">", 30) + trigger.action.outText("+++gTrk: group does not exist when adding to tracker <" .. trackerName .. ">", 30) return end + local theTracker = groupTracker.getTrackerByName(trackerName) + if not theTracker then return end groupTracker.addGroupToTracker(theGroup, theTracker) end +function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName) + local theTracker = groupTracker.getTrackerByName(trackerName) + if not theTracker then return end + if not gName then + trigger.action.outText("+++gTrk: group name in removeGroupNameFromTrackerNamed <" .. trackerName .. ">", 30) + return + end + + local filteredGroups = {} + local foundOne = false + for idx, aGroup in pairs(theTracker.trackedGroups) do + if aGroup:getName() == gName then + -- skip and remember + foundOne = true + else + table.insert(filteredGroups, aGroup) + end + end + if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then + trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. trackerName .. ">", 30) + end + + -- remember the new, cleanded set + theTracker.trackedGroups = filteredGroups + + + if foundOne then + if theTracker.verbose or groupTracker.verbose then + trigger.action.outText("+++gTrk: removed group <" .. gName .. "> from tracker <" .. trackerName .. ">", 30) + end + + -- now bang/invoke addGroup! + if theTracker.tRemoveGroup then + cfxZones.pollFlag(theTracker.tRemoveGroup, "inc", theTracker) + end + + -- now set numGroups + if theTracker.tNumGroups then + cfxZones.setFlagValue(theTracker.tNumGroups, #theTracker.trackedGroups, theTracker) + end + end +end + -- read zone function groupTracker.createTrackerWithZone(theZone) -- init group tracking set diff --git a/modules/impostors.lua b/modules/impostors.lua new file mode 100644 index 0000000..5ac854b --- /dev/null +++ b/modules/impostors.lua @@ -0,0 +1,559 @@ +impostors={} + +impostors.version = "1.0.0" +impostors.verbose = false +impostors.ups = 1 +impostors.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course + "cfxMX", +} + +impostors.impostorZones = {} +--impostors.impostors = {} -- these are sorted by name of the orig group and contain the units by name of the impostors +impostors.callbacks = {} +impostors.uniqueCounter = 8200000 -- clones start at 9200000 + +--[[-- + LIMITATIONS: + must be on ground (or would be very silly + does not work with any units deployed on ships + Positioning AI Planed (ME shortcoming): add a waypoint so it orients itself. + +--]]-- + +-- +-- adding / removing from list +-- +function impostors.addImpostorZone(theZone) + table.insert(impostors.impostorZones, theZone) +end + +function impostors.getCloneZoneByName(aName) + for idx, aZone in pairs(impostors.impostorZones) do + if aName == aZone.name then return aZone end + end + if impostors.verbose then + trigger.action.outText("+++ipst: no impostor with name <" .. aName ..">", 30) + end + + return nil +end + + +-- +-- spawn impostors from data +-- +function impostors.uniqueID() + local uid = impostors.uniqueCounter + impostors.uniqueCounter = impostors.uniqueCounter + 1 + return uid +end + +function impostors.spawnImpostorsFromData(rawData, cat, ctry) + local theImpostors = {} + -- we iterate a group's raw data unit by unit and create + -- a static for each unit, named exactly as the original unit + -- modifies rawData for use later + for idx, unitData in pairs(rawData.units) do + -- build impostor record + local ir = {} + ir.heading = unitData.heading + ir.type = unitData.type + ir.name = rawData.name .. "-" .. tostring(impostors.uniqueID()) + ir.groupID = impostors.uniqueID() + ir.unitId = impostors.uniqueID() + theImpostors[unitData.name] = ir.name -- for lookup later + ir.x = unitData.x + ir.y = unitData.y + ir.livery_id = unitData.livery_id + + if impostors.verbose then + trigger.action.outText("+++impostoring unit <" .. unitData.name .. ">: name <" .. ir.name .. ">, x <" .. ir.x .. ">, y <" .. ir.y .. ">, heading <" .. ir.heading .. ">, type <" .. ir.type .. "> ", 30) + end + local linkedZones = unitData.linkedZones + -- spawn the impostor + local theImp = coalition.addStaticObject(ctry, ir) + -- relink linked zones to this + if #linkedZones > 0 then + for idx, theZone in pairs(linkedZones) do + theZone.linkedUnit = theImp + if theZone.verbose then + trigger.action.outText("+++ipst: imp-linked zone <" .. theZone.name .. "> to imp <" .. theImp:getName() .. ">", 30) + end + end + end + end + return theImpostors +end +-- +-- read impostor zone +-- + +function impostors.getRawDataFromGroupNamed(gName) + if gName then + theGroup = Group.getByName(gName) + if not theGroup then + trigger.action.outText("+++ipst: getRawDataFromGroupName cant find group <" .. gName .. ">", 30) + return nil, nil, nil + end + else + trigger.action.outText("+++ipst: getRawDataFromGroupName has no name to look up", 30) + return nil, nil, nil + end + local groupName = gName + local cat = theGroup:getCategory() + -- access mxdata for livery because getDesc does not return the livery + local liveries = {} + local mxData = cfxMX.getGroupFromDCSbyName(gName) + for idx, theUnit in pairs (mxData.units) do + liveries[theUnit.name] = theUnit.livery_id + end + + local ctry + local gID = theGroup:getID() + local allUnits = theGroup:getUnits() + local rawGroup = {} + rawGroup.name = groupName + local rawUnits = {} + for idx, theUnit in pairs(allUnits) do + local ir = {} + local unitData = theUnit:getDesc() + -- build record + ir.heading = dcsCommon.getUnitHeading(theUnit) + ir.name = theUnit:getName() + ir.type = unitData.typeName -- warning: fields are called differently! typename vs type + ir.livery_id = liveries[ir.name] -- getDesc does not return livery + ir.groupId = gID + ir.unitId = theUnit:getID() + local up = theUnit:getPoint() + ir.x = up.x + ir.y = up.z -- !!! warning! + -- see if any zones are linked to this unit + ir.linkedZones = cfxZones.zonesLinkedToUnit(theUnit) + + table.insert(rawUnits, ir) + ctry = theUnit:getCountry() + end + rawGroup.ctry = ctry + rawGroup.cat = cat + rawGroup.units = rawUnits + return rawGroup, cat, ctry +end + + +function impostors.createImpostorWithZone(theZone) -- has "impostor?" + if impostors.verbose or theZone.verbose then + trigger.action.outText("+++ipst: new impostor " .. theZone.name, 30) + end + + -- the impostor? is the flag. we always have it. + -- must match aZone.impostorFlag, aZone.impostorTriggerMethod, "lastImpostorValue" + theZone.impostorFlag = cfxZones.getStringFromZoneProperty(theZone, "impostor?", "*") + theZone.lastImpostorValue = cfxZones.getFlagValue(theZone.impostorFlag, theZone) + + if cfxZones.hasProperty(theZone, "reanimate?") then + theZone.reanimateFlag = cfxZones.getStringFromZoneProperty(theZone, "reanimate?", "*") + theZone.lastReanimateValue = cfxZones.getFlagValue(theZone.reanimateFlag, theZone) + end + + -- watchflag: + -- triggerMethod + theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + + if cfxZones.hasProperty(theZone, "impostorTriggerMethod") then + theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "impostorTriggerMethod", "change") + end + +-- local localGroups = impostors.allGroupsInZoneByData(theZone) + theZone.groupNames = cfxZones.allGroupNamesInZone(theZone) + theZone.impostor = false -- we have not yet turned units into impostors + theZone.myImpostors = {} + theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors + + theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) + + -- blinking + theZone.blinkTime = cfxZones.getNumberFromZoneProperty(theZone, "blink", -1) + theZone.blinkCount = 0 + + -- interface to groupTracker tbc + if cfxZones.hasProperty(theZone, "trackWith:") then + theZone.trackWith = cfxZones.getStringFromZoneProperty(theZone, "trackWith:", "") + end + + -- check onStart, and act accordingly + if theZone.onStart then + impostors.turnZoneIntoImpostors(theZone) + end + + -- all dead + if cfxZones.hasProperty(theZone, "allDead!") then + theZone.allDead = cfxZones.getStringFromZoneProperty(theZone, "allDead", "") + end + + theZone.impostorMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") + if cfxZones.hasProperty(theZone, "impostorMethod") then + theZone.impostorMethod = cfxZones.getStringFromZoneProperty(theZone, "impostorMethod", "inc") + end + + -- declare all units as alive + theZone.allImpsDead = false + -- we end with group replaced by impostors +end + + +-- +-- Spawning +-- + +-- REAL --> IMP +function impostors.turnGroupsIntoImpostors(theZone) +-- can be handed an array of strings or groups. +-- returns a dict of impostors, indexed by group names + local theGroupNames = theZone.groupNames + local myImpostors = {} + for idx, aGroupName in pairs(theGroupNames) do + local gName = aGroupName + if type(gName) == "table" then -- this is a group. get its name + trigger.action.outText("+++ipst: converting table gName to string in turnGroupsIntoImpostors",30) + gName = gName:getName() + end + if not gName then + trigger.action.outText("+++ipst: nil group name in turnGroupsIntoImpostors",30) + return nil + end + local aGroup = Group.getByName(gName) + if aGroup and gName then + -- record unit data to create impostors + local rawData, cat, ctry = impostors.getRawDataFromGroupNamed(gName) + -- if we are tracking the group, remove it from tracker + if theZone.trackWith and groupTracker.removeGroupNamedFromTrackerNamed then + groupTracker.removeGroupNamedFromTrackerNamed(gName, theZone.trackWith) + end + + -- despawn the group. we'll spawn statics now + -- we may do some book-keeping first for the + -- names. we'll see later + Group.destroy(aGroup) +-- local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName) +-- local origID = rawData.groupId -- may be redundant + -- now spawn impostors based on the rawData, + -- and return impostorGroup + local impostorGroup = impostors.spawnImpostorsFromData(rawData, cat, ctry) + myImpostors[gName] = impostorGroup + end + end + return myImpostors +end + +function impostors.turnZoneIntoImpostors(theZone) + if theZone.verbose then + trigger.action.outText("+++ipst: creating impostors for zone <" .. theZone.name ..">", 30) + end + theZone.myImpostors = impostors.turnGroupsIntoImpostors(theZone) + if theZone.myImpostors then + theZone.impostor = true + else + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: groups to impostors failed for <" .. theZone.name .. ">",30) + end + end +end + +-- IMP --> REAL +function impostors.relinkZonesForGroup(relinkZones, newGroup) + -- may be called sync and async! + local allUnits = newGroup:getUnits() + for idx, theUnit in pairs(allUnits) do + local unitName = theUnit:getName() + local linkedZones = relinkZones[unitName] + if linkedZones and #linkedZones > 0 then + for idy, theZone in pairs(linkedZones) do + theZone.linkedUnit = theUnit + if theZone.verbose then + trigger.action.outText("+++ipst: re-linked zone <" .. theZone.name .. "> to unit <" .. unitName .. ">", 30) + end + end + end + end +end + +function impostors.spawnGroupsFromImpostor(theZone) + -- turn zone's impostors (static objects) into units + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: spawning for impostor <" .. theZone.name .. ">", 30) + end + + if not theZone.impostor then + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: <> groups are not impostors.", 30) + end + return + end + + local deadUnits = {} -- collect all dead units for immediate delete + -- after spawning + + for idx, groupName in pairs(theZone.groupNames) do + -- get my group data from MX based on my name + -- we get from MX so we get all path and order info + local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(groupName) + local impostorGroup = theZone.myImpostors[groupName] + local relinkZones = {} + -- now iterate all units in that group, and remove their impostors + for idy, theUnit in pairs(rawData.units) do + local impName = impostorGroup[theUnit.name] + local impStat = StaticObject.getByName(impName) + if impStat and impStat:isExist() and impStat:getLife() > 1 then + -- still alive. read x, y and heading + local sp = impStat:getPoint() + theUnit.x = sp.x + theUnit.y = sp.z -- !!! + theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics + -- should automatically handle ["livery_id"] + relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat) + else + -- dead + table.insert(deadUnits, theUnit.name) + end + -- destroy imp + if impStat and impStat:isExist() then + impStat:destroy() + end + end + + -- destroy impostor info + theZone.myImpostors[groupName] = nil + theZone.impostor = false + + -- now create the group + if theZone.blinkTime <= 0 then + -- immediate spawn + local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData) + impostors.relinkZonesForGroup(relinkZones, newGroup) + if theZone.trackWith and groupTracker.addGroupToTrackerNamed then + -- add these groups to the group tracker + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: attempting to add group <" .. newGroup:getName() .. "> to tracker <" .. theZone.trackWith .. ">", 30) + end + groupTracker.addGroupToTrackerNamed(newGroup, theZone.trackWith) + end + else + -- scheduled spawn + theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives + local args = {} + args.ctry = ctry + args.cat = cfxMX.catText2ID(cat) + args.rawData = rawData + args.theZone = theZone + args.relinkZones = relinkZones + timer.scheduleFunction(impostors.delayedSpawn, args, timer.getTime() + theZone.blinkTime) + end + end + + -- now remove all dead units + if theZone.blinkTime <= 0 then + for idx, unitName in pairs(deadUnits) do + local theUnit = Unit.getByName(unitName) + if theUnit then + theUnit:destroy() -- BAD BAD BAD!!!! do some guarding, mon! + end + end + else + -- schedule removal of all dead units for later + timer.scheduleFunction(impostors.delayedCleanup, deadUnits, timer.getTime() + theZone.blinkTime + 0.1) + end + theZone.myImpostors = nil +end + +function impostors.delayedSpawn(args) + local rawData = args.rawData + local cat = args.cat + local ctry = args.ctry + local theZone = args.theZone + local relinkZones = args.relinkZones + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: delayed spawn for group <" .. rawData.name .. "> of zone <" .. theZone.name .. ">", 30) + end + local newGroup = coalition.addGroup(ctry, cat, rawData) + impostors.relinkZonesForGroup(relinkZones, newGroup) + + if theZone.trackWith and groupTracker.addGroupToTrackerNamed then + -- add these groups to the group tracker + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: attempting to add group <" .. newGroup:getName() .. "> to tracker <" .. theZone.trackWith .. ">", 30) + end + groupTracker.addGroupToTrackerNamed(newGroup, theZone.trackWith) + end + -- close TRX bracket for blinking, health check can proceed + theZone.blinkCount = theZone.blinkCount - 1 +end + +function impostors.delayedCleanup(deadUnits) + for idx, unitName in pairs(deadUnits) do + local theUnit = Unit.getByName(unitName) + if theUnit then + theUnit:destroy() -- BAD BAD BAD!!!! do some guarding, mon! + end + end +end + +-- +-- healthCheck +-- +function impostors.healthCheck(theZone) + -- make sure there is at least one living unit left + -- if not, bang! + if theZone.allImpsDead then return end -- we are already dead + + if theZone.impostor then + -- we have impostors. Check until you find the first live ones + for gName, impNames in pairs (theZone.myImpostors) do + -- impNames are the names of all the static objects + -- in this group + for idx, theImpName in pairs (impNames) do + local theImp = StaticObject.getByName(theImpName) + if theImp and theImp:isExist() then + local life = theImp:getLife() + if life > 1 then + -- all is well, at least one imp alive + return + end + end + end + end + -- when we get here, all imps are dead, + -- drop through + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: Zone <" .. theZone.name .. "> - all impostors destroyed. Removing.", 30) + end + else + -- we have real groups. Let's iterate + if theZone.blinkCount > 0 then return end -- blinking, no healtch check + theZone.BlinkCount = 0 -- just in case it went negative + + for idx, groupName in pairs(theZone.groupNames) do + local theGroup = Group.getByName(groupName) + if theGroup and theGroup:isExist() then + local allUnits = theGroup:getUnits() + for idy, aUnit in pairs (allUnits) do + if aUnit:isExist() then + local life = aUnit:getLife() + if life > 1 then + return -- all is well + end + end + end + end + end + -- if we get here, all units ded + if theZone.verbose or impostors.verbose then + trigger.action.outText("+++ipst: Zone <" .. theZone.name .. "> - all active units destroyed. Removing.", 30) + end + end + + -- when we get here , all are ded + theZone.allImpsDead = true + if theZone.allDead then + cfxZones.pollFlag(theZone.allDead, theZone.impostorMethod, theZone) + end +end + +-- +-- Update +-- +function impostors.update() + timer.scheduleFunction(impostors.update, {}, timer.getTime() + 1/impostors.ups) + + for idx, aZone in pairs(impostors.impostorZones) do + -- first perform health check on all zones + impostors.healthCheck(aZone) + + -- now see if we received signals + if not aZone.allImpsDead then + -- see if we got impostor? command + if cfxZones.testZoneFlag(aZone, aZone.impostorFlag, aZone.impostorTriggerMethod, "lastImpostorValue") then + if impostors.verbose or aZone.verbose then + trigger.action.outText("+++ipst: turn group to impostors triggered for <" .. aZone.name .. "> on <" .. aZone.impostorFlag .. ">", 30) + end + impostors.turnZoneIntoImpostors(aZone) + end + + if aZone.reanimateFlag and cfxZones.testZoneFlag(aZone, aZone.reanimateFlag, aZone.impostorTriggerMethod, "lastReanimateValue") then + if impostors.verbose or aZone.verbose then + trigger.action.outText("+++ipst: impostor to live groups spawn triggered for <" .. aZone.name .. "> on <" .. aZone.impostorFlag .. ">", 30) + end + impostors.spawnGroupsFromImpostor(aZone) + end + else + -- nothing to do, all dead + end + end +end + + +-- +-- start +-- +function impostors.readConfigZone() + local theZone = cfxZones.getZoneByName("impostorsConfig") + if not theZone then + if impostors.verbose then + trigger.action.outText("+++ipst: NO config zone!", 30) + end + theZone = cfxZones.createSimpleZone("impostorsConfig") + end + + impostors.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + impostors.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + if impostors.verbose then + trigger.action.outText("+++ipst: read config", 30) + end +end + +function impostors.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx Impostors requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Impostors", + impostors.requiredLibs) then + return false + end + + -- read config + impostors.readConfigZone() + + -- process cloner Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("impostor?") + + -- now create an rnd gen for each one and add them + -- to our watchlist + for k, aZone in pairs(attrZones) do + impostors.createImpostorWithZone(aZone) -- process attribute and add to zone + impostors.addImpostorZone(aZone) -- remember it so we can smoke it + end + + -- start update + impostors.update() + + trigger.action.outText("cfx Impostors v" .. impostors.version .. " started.", 30) + return true +end + +-- let's go! +if not impostors.start() then + trigger.action.outText("cf/x Impostors aborted: missing libraries", 30) + impostors = nil +end + +--[[-- +To do +- reset? flag: will reset all to MX locationS +- add a zone's follow ability to impostors by allowing linkedUnit to work with impostors + +--]]-- \ No newline at end of file diff --git a/modules/radioMenus.lua b/modules/radioMenus.lua index b49701c..407b9fa 100644 --- a/modules/radioMenus.lua +++ b/modules/radioMenus.lua @@ -253,4 +253,5 @@ end --[[-- to do: turn on/off via flags callbacks for the menus + one-shot items --]]-- \ No newline at end of file diff --git a/tutorial & demo missions/demo - Impossible Impostors.miz b/tutorial & demo missions/demo - Impossible Impostors.miz new file mode 100644 index 0000000..8bb56ab Binary files /dev/null and b/tutorial & demo missions/demo - Impossible Impostors.miz differ