mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 2.3.4
Tickle, json persistence bug for cyclic structures
This commit is contained in:
parent
a2e8ead577
commit
a70fadc550
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
cfxMX = {}
|
cfxMX = {}
|
||||||
cfxMX.version = "2.1.0"
|
cfxMX.version = "2.2.0"
|
||||||
cfxMX.verbose = false
|
cfxMX.verbose = false
|
||||||
--[[--
|
--[[--
|
||||||
Mission data decoder. Access to ME-built mission structures
|
Mission data decoder. Access to ME-built mission structures
|
||||||
@ -18,11 +18,13 @@ cfxMX.verbose = false
|
|||||||
- new isDynamicPlayer()
|
- new isDynamicPlayer()
|
||||||
- new isMEPlayer()
|
- new isMEPlayer()
|
||||||
- new isMEPlayerGroup()
|
- new isMEPlayerGroup()
|
||||||
|
2.2.0 - new groupCatByName[]
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxMX.groupNamesByID = {}
|
cfxMX.groupNamesByID = {}
|
||||||
cfxMX.groupIDbyName = {}
|
cfxMX.groupIDbyName = {}
|
||||||
cfxMX.unitIDbyName = {}
|
cfxMX.unitIDbyName = {}
|
||||||
|
cfxMX.groupCatByName = {}
|
||||||
cfxMX.groupDataByName = {}
|
cfxMX.groupDataByName = {}
|
||||||
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
|
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
|
||||||
cfxMX.groupCoalitionByName = {}
|
cfxMX.groupCoalitionByName = {}
|
||||||
@ -226,7 +228,6 @@ function cfxMX.createCrossReferences()
|
|||||||
category = "train"
|
category = "train"
|
||||||
obj_type_name = "train"
|
obj_type_name = "train"
|
||||||
end
|
end
|
||||||
|
|
||||||
cfxMX.groupTypeByName[aName] = category
|
cfxMX.groupTypeByName[aName] = category
|
||||||
cfxMX.groupNamesByID[aID] = aName
|
cfxMX.groupNamesByID[aID] = aName
|
||||||
cfxMX.groupIDbyName[aName] = aID
|
cfxMX.groupIDbyName[aName] = aID
|
||||||
@ -237,16 +238,22 @@ function cfxMX.createCrossReferences()
|
|||||||
-- now make the type-specific xrefs
|
-- now make the type-specific xrefs
|
||||||
if obj_type_name == "helicopter" then
|
if obj_type_name == "helicopter" then
|
||||||
cfxMX.allHeloByName[aName] = group_data
|
cfxMX.allHeloByName[aName] = group_data
|
||||||
|
cfxMX.groupCatByName[aName] = 1
|
||||||
elseif obj_type_name == "ship" then
|
elseif obj_type_name == "ship" then
|
||||||
cfxMX.allSeaByName[aName] = group_data
|
cfxMX.allSeaByName[aName] = group_data
|
||||||
|
cfxMX.groupCatByName[aName] = 3
|
||||||
elseif obj_type_name == "plane" then
|
elseif obj_type_name == "plane" then
|
||||||
cfxMX.allFixedByName[aName] = group_data
|
cfxMX.allFixedByName[aName] = group_data
|
||||||
|
cfxMX.groupCatByName[aName] = 0
|
||||||
elseif obj_type_name == "vehicle" then
|
elseif obj_type_name == "vehicle" then
|
||||||
cfxMX.allGroundByName[aName] = group_data
|
cfxMX.allGroundByName[aName] = group_data
|
||||||
|
cfxMX.groupCatByName[aName] = 2
|
||||||
elseif obj_type_name == "static" then
|
elseif obj_type_name == "static" then
|
||||||
cfxMX.allStaticByName[aName] = group_data
|
cfxMX.allStaticByName[aName] = group_data
|
||||||
|
-- cfxMX.groupCatByName[aName] = -1 -- not covered
|
||||||
elseif obj_type_name == "train" then
|
elseif obj_type_name == "train" then
|
||||||
cfxMX.allTrainsByName[aName] = group_data
|
cfxMX.allTrainsByName[aName] = group_data
|
||||||
|
cfxMX.groupCatByName[aName] = 4
|
||||||
else
|
else
|
||||||
-- should be impossible, but still
|
-- should be impossible, but still
|
||||||
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)
|
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)
|
||||||
|
|||||||
@ -953,6 +953,9 @@ function cfxGroundTroops.addGroundTroopsToPool(troops) -- troops MUST be a table
|
|||||||
end
|
end
|
||||||
if not troops.orders then troops.orders = "guard" end
|
if not troops.orders then troops.orders = "guard" end
|
||||||
troops.orders = troops.orders:lower()
|
troops.orders = troops.orders:lower()
|
||||||
|
|
||||||
|
--trigger.action.outText("Enter adding group <" .. troops.name .. "> with orders=<" .. troops.orders .. "> to pool", 30)
|
||||||
|
|
||||||
if not troops.moveFormation then troops.moveFormation = "Custom" end
|
if not troops.moveFormation then troops.moveFormation = "Custom" end
|
||||||
troops.reschedule = true -- in case we use scheduled update
|
troops.reschedule = true -- in case we use scheduled update
|
||||||
-- we now add to internal array. this is worked on by all
|
-- we now add to internal array. this is worked on by all
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxHeloTroops = {}
|
cfxHeloTroops = {}
|
||||||
cfxHeloTroops.version = "3.1.2"
|
cfxHeloTroops.version = "3.1.3"
|
||||||
cfxHeloTroops.verbose = false
|
cfxHeloTroops.verbose = false
|
||||||
cfxHeloTroops.autoDrop = true
|
cfxHeloTroops.autoDrop = true
|
||||||
cfxHeloTroops.autoPickup = false
|
cfxHeloTroops.autoPickup = false
|
||||||
@ -20,6 +20,10 @@ cfxHeloTroops.requestRange = 500 -- meters
|
|||||||
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
|
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
|
||||||
3.1.1 - deployTroopsFromHelicopter() captureandhold
|
3.1.1 - deployTroopsFromHelicopter() captureandhold
|
||||||
3.1.2 - doLoadGroup - test if group is still alive edge case handling
|
3.1.2 - doLoadGroup - test if group is still alive edge case handling
|
||||||
|
3.1.3 - decycled structures (destination zone) on save
|
||||||
|
- upcycled structures (destination) on load
|
||||||
|
- loadSound and disembarkSound
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
|
|
||||||
@ -658,7 +662,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
troop.destination = dest -- transfer target zone for attackzone oders
|
troop.destination = dest -- transfer target zone for attackzone oders
|
||||||
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
|
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
|
||||||
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
|
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
|
||||||
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound)
|
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.disembarkSound)
|
||||||
-- see if this is tracked by a tracker, and pass them back so
|
-- see if this is tracked by a tracker, and pass them back so
|
||||||
-- they can un-limbo
|
-- they can un-limbo
|
||||||
if groupTracker then
|
if groupTracker then
|
||||||
@ -747,7 +751,7 @@ function cfxHeloTroops.doLoadGroup(args)
|
|||||||
|
|
||||||
-- say so
|
-- say so
|
||||||
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
|
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
|
||||||
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav")
|
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.loadSound) -- "Quest Snare 3.wav")
|
||||||
|
|
||||||
-- reset menu
|
-- reset menu
|
||||||
cfxHeloTroops.removeComms(conf.unit)
|
cfxHeloTroops.removeComms(conf.unit)
|
||||||
@ -898,6 +902,8 @@ function cfxHeloTroops.readConfigZone()
|
|||||||
cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200)
|
cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200)
|
||||||
|
|
||||||
cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
|
cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
|
||||||
|
cfxHeloTroops.loadSound = theZone:getStringFromZoneProperty("loadSound", cfxHeloTroops.actionSound)
|
||||||
|
cfxHeloTroops.disembarkSound = theZone:getStringFromZoneProperty("disembarkSound", cfxHeloTroops.actionSound)
|
||||||
|
|
||||||
cfxHeloTroops.requestRange = theZone:getNumberFromZoneProperty("requestRange", 500)
|
cfxHeloTroops.requestRange = theZone:getNumberFromZoneProperty("requestRange", 500)
|
||||||
-- add own troop carriers
|
-- add own troop carriers
|
||||||
@ -920,6 +926,10 @@ function cfxHeloTroops.saveData()
|
|||||||
for gName, gData in pairs(cfxHeloTroops.deployedTroops) do
|
for gName, gData in pairs(cfxHeloTroops.deployedTroops) do
|
||||||
local sData = dcsCommon.clone(gData)
|
local sData = dcsCommon.clone(gData)
|
||||||
dcsCommon.synchGroupData(sData.groupData)
|
dcsCommon.synchGroupData(sData.groupData)
|
||||||
|
if sData.destination then
|
||||||
|
net.log("cfxHeloTroops: decycling troop 'destination' for <" .. sData.destination:getName() .. ">")
|
||||||
|
sData.destination = sData.destination:getName()
|
||||||
|
end
|
||||||
allTroopData[gName] = sData
|
allTroopData[gName] = sData
|
||||||
end
|
end
|
||||||
theData.troops = allTroopData
|
theData.troops = allTroopData
|
||||||
@ -947,6 +957,13 @@ function cfxHeloTroops.loadData()
|
|||||||
local range = gdTroop.range
|
local range = gdTroop.range
|
||||||
local cty = gData.cty
|
local cty = gData.cty
|
||||||
local cat = gData.cat
|
local cat = gData.cat
|
||||||
|
local dest = nil
|
||||||
|
|
||||||
|
-- synch destination from name to real zone
|
||||||
|
if gdTroop.destination then
|
||||||
|
dest = cfxZones.getZoneByName(gdTroop.destination)
|
||||||
|
net.log("cfxHeloTroops: attempting to restore troop destination zone <" .. gdTroop.destination .. ">")
|
||||||
|
end
|
||||||
|
|
||||||
-- now spawn, but first
|
-- now spawn, but first
|
||||||
-- add to my own deployed queue so we can save later
|
-- add to my own deployed queue so we can save later
|
||||||
@ -957,6 +974,7 @@ function cfxHeloTroops.loadData()
|
|||||||
|
|
||||||
-- add to groundTroops
|
-- add to groundTroops
|
||||||
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
|
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
|
||||||
|
newTroops.destination = dest
|
||||||
cfxGroundTroops.addGroundTroopsToPool(newTroops)
|
cfxGroundTroops.addGroundTroopsToPool(newTroops)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
impostors={}
|
impostors={}
|
||||||
|
|
||||||
impostors.version = "1.0.1"
|
impostors.version = "1.1.0"
|
||||||
impostors.verbose = false
|
impostors.verbose = false
|
||||||
impostors.ups = 1
|
impostors.ups = 1
|
||||||
impostors.requiredLibs = {
|
impostors.requiredLibs = {
|
||||||
@ -18,7 +18,9 @@ impostors.uniqueCounter = 8200000 -- clones start at 9200000
|
|||||||
Version History
|
Version History
|
||||||
1.0.0 - initial version
|
1.0.0 - initial version
|
||||||
1.0.1 - added some verbosity
|
1.0.1 - added some verbosity
|
||||||
|
1.1.0 - filtered dead units during spawns
|
||||||
|
cleanup
|
||||||
|
some performance boost for mx lookup
|
||||||
|
|
||||||
LIMITATIONS:
|
LIMITATIONS:
|
||||||
must be on ground (or would be very silly
|
must be on ground (or would be very silly
|
||||||
@ -41,11 +43,9 @@ function impostors.getCloneZoneByName(aName)
|
|||||||
if impostors.verbose then
|
if impostors.verbose then
|
||||||
trigger.action.outText("+++ipst: no impostor with name <" .. aName ..">", 30)
|
trigger.action.outText("+++ipst: no impostor with name <" .. aName ..">", 30)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- spawn impostors from data
|
-- spawn impostors from data
|
||||||
--
|
--
|
||||||
@ -110,7 +110,9 @@ function impostors.getRawDataFromGroupNamed(gName)
|
|||||||
local cat = theGroup:getCategory()
|
local cat = theGroup:getCategory()
|
||||||
-- access mxdata for livery because getDesc does not return the livery
|
-- access mxdata for livery because getDesc does not return the livery
|
||||||
local liveries = {}
|
local liveries = {}
|
||||||
local mxData = cfxMX.getGroupFromDCSbyName(gName)
|
-- local mxData = cfxMX.getGroupFromDCSbyName(gName)
|
||||||
|
local mxData = cfxMX.groupDataByName[gName] -- performance
|
||||||
|
if mxData then mxData = dcsCommon.clone(mxData) end
|
||||||
for idx, theUnit in pairs (mxData.units) do
|
for idx, theUnit in pairs (mxData.units) do
|
||||||
liveries[theUnit.name] = theUnit.livery_id
|
liveries[theUnit.name] = theUnit.livery_id
|
||||||
end
|
end
|
||||||
@ -136,8 +138,9 @@ function impostors.getRawDataFromGroupNamed(gName)
|
|||||||
ir.y = up.z -- !!! warning!
|
ir.y = up.z -- !!! warning!
|
||||||
-- see if any zones are linked to this unit
|
-- see if any zones are linked to this unit
|
||||||
ir.linkedZones = cfxZones.zonesLinkedToUnit(theUnit)
|
ir.linkedZones = cfxZones.zonesLinkedToUnit(theUnit)
|
||||||
|
if theUnit:getLife() > 1 then
|
||||||
table.insert(rawUnits, ir)
|
table.insert(rawUnits, ir)
|
||||||
|
end
|
||||||
ctry = theUnit:getCountry()
|
ctry = theUnit:getCountry()
|
||||||
end
|
end
|
||||||
rawGroup.ctry = ctry
|
rawGroup.ctry = ctry
|
||||||
@ -161,21 +164,15 @@ function impostors.createImpostorWithZone(theZone) -- has "impostor?"
|
|||||||
theZone.reanimateFlag = cfxZones.getStringFromZoneProperty(theZone, "reanimate?", "*<none>")
|
theZone.reanimateFlag = cfxZones.getStringFromZoneProperty(theZone, "reanimate?", "*<none>")
|
||||||
theZone.lastReanimateValue = cfxZones.getFlagValue(theZone.reanimateFlag, theZone)
|
theZone.lastReanimateValue = cfxZones.getFlagValue(theZone.reanimateFlag, theZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- watchflag:
|
|
||||||
-- triggerMethod
|
|
||||||
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||||
|
|
||||||
if cfxZones.hasProperty(theZone, "impostorTriggerMethod") then
|
if cfxZones.hasProperty(theZone, "impostorTriggerMethod") then
|
||||||
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "impostorTriggerMethod", "change")
|
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "impostorTriggerMethod", "change")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- local localGroups = impostors.allGroupsInZoneByData(theZone)
|
|
||||||
theZone.groupNames = cfxZones.allGroupNamesInZone(theZone)
|
theZone.groupNames = cfxZones.allGroupNamesInZone(theZone)
|
||||||
theZone.impostor = false -- we have not yet turned units into impostors
|
theZone.impostor = false -- we have not yet turned units into impostors
|
||||||
theZone.myImpostors = {}
|
theZone.myImpostors = {}
|
||||||
theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors
|
theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors
|
||||||
|
|
||||||
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
|
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
|
||||||
|
|
||||||
-- blinking
|
-- blinking
|
||||||
@ -207,7 +204,6 @@ function impostors.createImpostorWithZone(theZone) -- has "impostor?"
|
|||||||
-- we end with group replaced by impostors
|
-- we end with group replaced by impostors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Spawning
|
-- Spawning
|
||||||
--
|
--
|
||||||
@ -230,6 +226,9 @@ function impostors.turnGroupsIntoImpostors(theZone)
|
|||||||
end
|
end
|
||||||
local aGroup = Group.getByName(gName)
|
local aGroup = Group.getByName(gName)
|
||||||
if aGroup and gName then
|
if aGroup and gName then
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("impostoring group <" .. gName .. ">", 30)
|
||||||
|
end
|
||||||
-- record unit data to create impostors
|
-- record unit data to create impostors
|
||||||
local rawData, cat, ctry = impostors.getRawDataFromGroupNamed(gName)
|
local rawData, cat, ctry = impostors.getRawDataFromGroupNamed(gName)
|
||||||
-- if we are tracking the group, remove it from tracker
|
-- if we are tracking the group, remove it from tracker
|
||||||
@ -241,8 +240,6 @@ function impostors.turnGroupsIntoImpostors(theZone)
|
|||||||
-- we may do some book-keeping first for the
|
-- we may do some book-keeping first for the
|
||||||
-- names. we'll see later
|
-- names. we'll see later
|
||||||
Group.destroy(aGroup)
|
Group.destroy(aGroup)
|
||||||
-- local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName)
|
|
||||||
-- local origID = rawData.groupId -- may be redundant
|
|
||||||
-- now spawn impostors based on the rawData,
|
-- now spawn impostors based on the rawData,
|
||||||
-- and return impostorGroup
|
-- and return impostorGroup
|
||||||
local impostorGroup = impostors.spawnImpostorsFromData(rawData, cat, ctry)
|
local impostorGroup = impostors.spawnImpostorsFromData(rawData, cat, ctry)
|
||||||
@ -303,28 +300,40 @@ function impostors.spawnGroupsFromImpostor(theZone)
|
|||||||
for idx, groupName in pairs(theZone.groupNames) do
|
for idx, groupName in pairs(theZone.groupNames) do
|
||||||
-- get my group data from MX based on my name
|
-- get my group data from MX based on my name
|
||||||
-- we get from MX so we get all path and order info
|
-- we get from MX so we get all path and order info
|
||||||
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(groupName)
|
-- local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(groupName)
|
||||||
|
local rawData = cfxMX.groupDataByName[groupName]
|
||||||
|
if rawData then rawData = dcsCommon.clone(rawData) end
|
||||||
|
local cat = cfxMX.groupCatByName[groupName]
|
||||||
|
local ctry = cfxMX.countryByName[groupName]
|
||||||
local impostorGroup = theZone.myImpostors[groupName]
|
local impostorGroup = theZone.myImpostors[groupName]
|
||||||
local relinkZones = {}
|
local relinkZones = {}
|
||||||
-- now iterate all units in that group, and remove their impostors
|
-- now iterate all units in that group, and remove their impostors
|
||||||
for idy, theUnit in pairs(rawData.units) do
|
for idy, theUnit in pairs(rawData.units) do
|
||||||
local impName = impostorGroup[theUnit.name]
|
if theUnit and theUnit.name then
|
||||||
local impStat = StaticObject.getByName(impName)
|
local impName = impostorGroup[theUnit.name]
|
||||||
if impStat and impStat:isExist() and impStat:getLife() > 1 then
|
if not impName then
|
||||||
-- still alive. read x, y and heading
|
if theZone.verbose then
|
||||||
local sp = impStat:getPoint()
|
trigger.action.outText("group <" .. groupName .. ">: no impostor for <" .. theUnit.name .. ">", 30)
|
||||||
theUnit.x = sp.x
|
end
|
||||||
theUnit.y = sp.z -- !!!
|
else
|
||||||
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
|
local impStat = StaticObject.getByName(impName)
|
||||||
-- should automatically handle ["livery_id"]
|
if impStat and impStat:isExist() and impStat:getLife() > 1 then
|
||||||
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
|
-- still alive. read x, y and heading
|
||||||
else
|
local sp = impStat:getPoint()
|
||||||
-- dead
|
theUnit.x = sp.x
|
||||||
table.insert(deadUnits, theUnit.name)
|
theUnit.y = sp.z -- !!!
|
||||||
end
|
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
|
||||||
-- destroy imp
|
-- should automatically handle ["livery_id"]
|
||||||
if impStat and impStat:isExist() then
|
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
|
||||||
impStat:destroy()
|
else
|
||||||
|
-- dead
|
||||||
|
table.insert(deadUnits, theUnit.name)
|
||||||
|
end
|
||||||
|
-- destroy imp
|
||||||
|
if impStat and impStat:isExist() then
|
||||||
|
impStat:destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -335,7 +344,8 @@ function impostors.spawnGroupsFromImpostor(theZone)
|
|||||||
-- now create the group
|
-- now create the group
|
||||||
if theZone.blinkTime <= 0 then
|
if theZone.blinkTime <= 0 then
|
||||||
-- immediate spawn
|
-- immediate spawn
|
||||||
local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
|
--local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
|
||||||
|
local newGroup = coalition.addGroup(ctry, cat, rawData)
|
||||||
impostors.relinkZonesForGroup(relinkZones, newGroup)
|
impostors.relinkZonesForGroup(relinkZones, newGroup)
|
||||||
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
|
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
|
||||||
-- add these groups to the group tracker
|
-- add these groups to the group tracker
|
||||||
@ -349,7 +359,7 @@ function impostors.spawnGroupsFromImpostor(theZone)
|
|||||||
theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives
|
theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives
|
||||||
local args = {}
|
local args = {}
|
||||||
args.ctry = ctry
|
args.ctry = ctry
|
||||||
args.cat = cfxMX.catText2ID(cat)
|
args.cat = cat -- cfxMX.catText2ID(cat)
|
||||||
args.rawData = rawData
|
args.rawData = rawData
|
||||||
args.theZone = theZone
|
args.theZone = theZone
|
||||||
args.relinkZones = relinkZones
|
args.relinkZones = relinkZones
|
||||||
@ -384,12 +394,6 @@ function impostors.delayedSpawn(args)
|
|||||||
local newGroup = coalition.addGroup(ctry, cat, rawData)
|
local newGroup = coalition.addGroup(ctry, cat, rawData)
|
||||||
impostors.relinkZonesForGroup(relinkZones, newGroup)
|
impostors.relinkZonesForGroup(relinkZones, newGroup)
|
||||||
|
|
||||||
if newGroup then
|
|
||||||
-- trigger.action.outText("+++ipst: SUCCESS!!! Spawned group <" .. newGroup:getName() .. "> for impostor <" .. rawData.name .. ">", 30)
|
|
||||||
else
|
|
||||||
trigger.action.outText("+++ipst: failed to spawn group for impostor <" .. rawData.name .. ">", 30)
|
|
||||||
end
|
|
||||||
|
|
||||||
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
|
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
|
||||||
-- add these groups to the group tracker
|
-- add these groups to the group tracker
|
||||||
if theZone.verbose or impostors.verbose then
|
if theZone.verbose or impostors.verbose then
|
||||||
@ -409,7 +413,6 @@ function impostors.delayedCleanup(deadUnits)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- healthCheck
|
-- healthCheck
|
||||||
--
|
--
|
||||||
@ -502,8 +505,6 @@ function impostors.update()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- start
|
-- start
|
||||||
--
|
--
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
persistence = {}
|
persistence = {}
|
||||||
persistence.version = "3.0.1"
|
persistence.version = "3.0.2"
|
||||||
persistence.ups = 1 -- once every 1 seconds
|
persistence.ups = 1 -- once every 1 seconds
|
||||||
persistence.verbose = false
|
persistence.verbose = false
|
||||||
persistence.active = false
|
persistence.active = false
|
||||||
@ -22,6 +22,8 @@ persistence.requiredLibs = {
|
|||||||
API cleanup
|
API cleanup
|
||||||
shared text data "flase" typo corrected (no impact)
|
shared text data "flase" typo corrected (no impact)
|
||||||
code cleanup
|
code cleanup
|
||||||
|
3.0.2 - more logging
|
||||||
|
vardump to log possible
|
||||||
|
|
||||||
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
||||||
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
|
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
|
||||||
@ -165,12 +167,17 @@ function persistence.saveText(theString, fileName, shared, append)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function persistence.saveTable(theTable, fileName, shared, append)
|
function persistence.saveTable(theTable, fileName, shared, append)
|
||||||
|
net.log("persistence: enter saveTable")
|
||||||
|
|
||||||
if not persistence.active then return false end
|
if not persistence.active then return false end
|
||||||
if not fileName then return false end
|
if not fileName then return false end
|
||||||
if not theTable then return false end
|
if not theTable then return false end
|
||||||
if not shared then shared = false end
|
if not shared then shared = false end
|
||||||
|
|
||||||
|
net.log("persistence: before json conversion")
|
||||||
local theString = net.lua2json(theTable)
|
local theString = net.lua2json(theTable)
|
||||||
|
net.log("persistence: json conversion complete")
|
||||||
|
|
||||||
if not theString then theString = "" end
|
if not theString then theString = "" end
|
||||||
local path = persistence.missionDir .. fileName
|
local path = persistence.missionDir .. fileName
|
||||||
if shared then
|
if shared then
|
||||||
@ -178,6 +185,7 @@ function persistence.saveTable(theTable, fileName, shared, append)
|
|||||||
path = persistence.sharedDir .. fileName .. ".txt"
|
path = persistence.sharedDir .. fileName .. ".txt"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
net.log("persistence: will now open file at path <" .. path .. ">")
|
||||||
local theFile = nil
|
local theFile = nil
|
||||||
if append then
|
if append then
|
||||||
theFile = io.open(path, "a")
|
theFile = io.open(path, "a")
|
||||||
@ -187,8 +195,11 @@ function persistence.saveTable(theTable, fileName, shared, append)
|
|||||||
if not theFile then
|
if not theFile then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
net.log("persistence: will now write file")
|
||||||
theFile:write(theString)
|
theFile:write(theString)
|
||||||
|
net.log("persistence: will now close file")
|
||||||
theFile:close()
|
theFile:close()
|
||||||
|
net.log("persistence: will now exit saveTable")
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -310,6 +321,35 @@ function persistence.missionStartDataLoad()
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- logging data
|
||||||
|
--
|
||||||
|
function persistence.logTable(key, value, prefix, inrecursion)
|
||||||
|
local comma = ""
|
||||||
|
if inrecursion then
|
||||||
|
if tonumber(key) then key = '[' .. key .. ']' else key = '["' .. key .. '"]' end
|
||||||
|
comma = ","
|
||||||
|
end
|
||||||
|
if not value then value = false end -- not NIL!
|
||||||
|
if not prefix then prefix = "" else prefix = "\t" .. prefix end
|
||||||
|
if type(value) == "table" then -- recursively write a table
|
||||||
|
net.log(prefix .. key .. " = \n" .. prefix .. "{\n")
|
||||||
|
for k,v in pairs (value) do -- iterate all kvp
|
||||||
|
persistence.logTable(k, v, prefix, true)
|
||||||
|
end
|
||||||
|
net.log(prefix .. "}" .. comma .. " -- end of " .. key .. "\n")
|
||||||
|
elseif type(value) == "boolean" then
|
||||||
|
local b = "false"
|
||||||
|
if value then b = "true" end
|
||||||
|
net.log(prefix .. key .. " = " .. b .. comma .. "\n")
|
||||||
|
elseif type(value) == "string" then -- quoted string, WITH proccing
|
||||||
|
value = string.gsub(value, "\\", "\\\\") -- escape "\" to "\\", others ignored, possibly conflict with \n
|
||||||
|
value = string.gsub(value, string.char(10), "\\" .. string.char(10)) -- 0A --> "\"0A
|
||||||
|
net.log(prefix .. key .. ' = "' .. value .. '"' .. comma .. "\n")
|
||||||
|
else -- simple var, show contents, ends recursion
|
||||||
|
net.log(prefix .. key .. " = " .. value .. comma .. "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
--
|
--
|
||||||
-- MAIN DATA WRITE
|
-- MAIN DATA WRITE
|
||||||
--
|
--
|
||||||
@ -342,9 +382,10 @@ function persistence.saveMissionData()
|
|||||||
|
|
||||||
-- now handle flags
|
-- now handle flags
|
||||||
myData["persistence.flagData"] = persistence.collectFlagData()
|
myData["persistence.flagData"] = persistence.collectFlagData()
|
||||||
|
net.log("persistence: --- START of module-individual save")
|
||||||
-- now handle all other modules
|
-- now handle all other modules
|
||||||
for moduleName, callbacks in pairs(persistence.callbacks) do
|
for moduleName, callbacks in pairs(persistence.callbacks) do
|
||||||
|
net.log("persistence: invoking save for module " .. moduleName)
|
||||||
local moduleData, sharedName = callbacks.persistData()
|
local moduleData, sharedName = callbacks.persistData()
|
||||||
if moduleData then
|
if moduleData then
|
||||||
if sharedName then -- save into shared bucket
|
if sharedName then -- save into shared bucket
|
||||||
@ -358,18 +399,30 @@ function persistence.saveMissionData()
|
|||||||
if persistence.verbose then
|
if persistence.verbose then
|
||||||
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
|
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
|
||||||
end
|
end
|
||||||
|
net.log("persistence: got data for module: " .. moduleName)
|
||||||
|
--persistence.logTable(moduleName, moduleData)
|
||||||
|
--net.log("persistence: performing json conversion test for myData")
|
||||||
|
--local theString = net.lua2json(myData)
|
||||||
|
--net.log("persistence: json conversion success!")
|
||||||
else
|
else
|
||||||
if persistence.verbose then
|
if persistence.verbose then
|
||||||
trigger.action.outText("+++persistence: NO DATA gathered data from <" .. moduleName .. ">, module returned NIL", 30)
|
trigger.action.outText("+++persistence: NO DATA gathered data from <" .. moduleName .. ">, module returned NIL", 30)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
net.log("persistence: completed save for module " .. moduleName)
|
||||||
end
|
end
|
||||||
|
net.log("persistence: --- END of module-individual save")
|
||||||
|
|
||||||
-- now save data to file
|
-- now save data to file
|
||||||
|
net.log("persistence: will now invoke main saveTable")
|
||||||
persistence.saveTable(myData, persistence.saveFileName)
|
persistence.saveTable(myData, persistence.saveFileName)
|
||||||
|
net.log("persistence: returned from main save table")
|
||||||
|
|
||||||
-- now save all shared name data as separate files
|
-- now save all shared name data as separate files
|
||||||
|
net.log("persistence: will now iterate shares")
|
||||||
for shareName, data in pairs (allSharedData) do
|
for shareName, data in pairs (allSharedData) do
|
||||||
|
net.log("persistence: share " .. shareName)
|
||||||
|
|
||||||
-- save into shared folder, by name that was returned from callback
|
-- save into shared folder, by name that was returned from callback
|
||||||
-- read what was saved, and replace changed key/values from data
|
-- read what was saved, and replace changed key/values from data
|
||||||
local shFile = persistence.sharedDir .. shareName .. ".txt"
|
local shFile = persistence.sharedDir .. shareName .. ".txt"
|
||||||
@ -384,12 +437,15 @@ function persistence.saveMissionData()
|
|||||||
|
|
||||||
persistence.saveTable(theData, shareName, true) -- true --> shared
|
persistence.saveTable(theData, shareName, true) -- true --> shared
|
||||||
end
|
end
|
||||||
|
net.log("persistence: done iterating shares")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- UPDATE
|
-- UPDATE
|
||||||
--
|
--
|
||||||
function persistence.doSaveMission()
|
function persistence.doSaveMission()
|
||||||
|
net.log("persistence: start doSaveMission")
|
||||||
-- main save entry, also from API
|
-- main save entry, also from API
|
||||||
if persistence.verbose then
|
if persistence.verbose then
|
||||||
trigger.action.outText("+++persistence: starting save", 30)
|
trigger.action.outText("+++persistence: starting save", 30)
|
||||||
@ -407,6 +463,7 @@ function persistence.doSaveMission()
|
|||||||
if persistence.saveNotification then
|
if persistence.saveNotification then
|
||||||
trigger.action.outText("+++persistence: mission saved to\n" .. persistence.missionDir .. persistence.saveFileName, 30)
|
trigger.action.outText("+++persistence: mission saved to\n" .. persistence.missionDir .. persistence.saveFileName, 30)
|
||||||
end
|
end
|
||||||
|
net.log("persistence: DONE doSaveMission")
|
||||||
end
|
end
|
||||||
|
|
||||||
function persistence.noteCleanRestart()
|
function persistence.noteCleanRestart()
|
||||||
|
|||||||
@ -18,6 +18,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
|
|||||||
3.3.0 - case INsensitivity for all typeScore objects
|
3.3.0 - case INsensitivity for all typeScore objects
|
||||||
3.3.1 - fixes for DCS oddity in events after update
|
3.3.1 - fixes for DCS oddity in events after update
|
||||||
- cleanup
|
- cleanup
|
||||||
|
|
||||||
TODO: Kill event no longer invoked for map objetcs, attribute
|
TODO: Kill event no longer invoked for map objetcs, attribute
|
||||||
to faction now, reverse invocation direction with PlayerScore
|
to faction now, reverse invocation direction with PlayerScore
|
||||||
--]]--
|
--]]--
|
||||||
|
|||||||
@ -27,6 +27,7 @@ function sweeper.readSweeperZone(theZone)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function sweeper.update()
|
function sweeper.update()
|
||||||
|
net.log("sweeper: begin update")
|
||||||
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
|
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
|
||||||
local toKill = {}
|
local toKill = {}
|
||||||
local newFlights = {}
|
local newFlights = {}
|
||||||
@ -105,6 +106,8 @@ function sweeper.update()
|
|||||||
|
|
||||||
-- remember new list, forget old
|
-- remember new list, forget old
|
||||||
sweeper.flights = newFlights
|
sweeper.flights = newFlights
|
||||||
|
net.log("sweeper: end update")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function sweeper.readConfig()
|
function sweeper.readConfig()
|
||||||
|
|||||||
355
server modules/tcliGUI.lua
Normal file
355
server modules/tcliGUI.lua
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
tcli = {}
|
||||||
|
tcli.version = "1.1.0"
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Tickle - a tiny DCS admin CLI (c) 2024 by Christian "CFrag" Franz
|
||||||
|
|
||||||
|
Version History
|
||||||
|
1.1.0 - endTime init to very, very late
|
||||||
|
- more mission begin load logging
|
||||||
|
- stronger guards for onXXX
|
||||||
|
- "-shuffle" and "-sequence" commands
|
||||||
|
- better -? response
|
||||||
|
- drove sample time up to 50 seconds between scans
|
||||||
|
- "-cycle" command
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
tcli.myConfig = lfs.writedir() .. "Missions\\" .. "tcli.config"
|
||||||
|
tcli.serverCfgPath = lfs.writedir() .. "Config\\" .. "serverSettings.lua"
|
||||||
|
tcli.lastTime = -1
|
||||||
|
local config = {}
|
||||||
|
config.mark = "-"
|
||||||
|
config.admins = {} -- who is allowed to command
|
||||||
|
config.cycleTime = -1 -- no cycles, auto miz change off
|
||||||
|
table.insert(config.admins, "xk76hkl@01") -- some silly user names.
|
||||||
|
table.insert(config.admins, "%tgJsgRG1<") -- change to your own in the config file that is created in Missions AFTER DCS starts up
|
||||||
|
tcli.config = config
|
||||||
|
|
||||||
|
-- utils
|
||||||
|
function tcli.hasFile(path) --check if file exists at path
|
||||||
|
local attr = lfs.attributes(path)
|
||||||
|
if attr then return true, attr.mode end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.loadFile(path)
|
||||||
|
if not path then return nil end
|
||||||
|
local theFile = io.open(path, "r")
|
||||||
|
if not theFile then return nil end
|
||||||
|
local t = theFile:read("*a")
|
||||||
|
theFile:close()
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.loadData(path) -- load file as lua table, full path in fileName
|
||||||
|
local t = tcli.loadFile(path)
|
||||||
|
if not t then return nil end
|
||||||
|
local d = net.json2lua(t)
|
||||||
|
return d
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.saveData(path, theData) -- save theData (table) as json text file
|
||||||
|
if not theData then return false end
|
||||||
|
local theString = net.lua2json(theData)
|
||||||
|
if not theString then theString = "" end
|
||||||
|
local theFile = nil
|
||||||
|
theFile = io.open(path, "w") -- overwrite
|
||||||
|
if not theFile then return false end
|
||||||
|
theFile:write(theString)
|
||||||
|
theFile:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.nameInTable(name, T)
|
||||||
|
for idx, aName in pairs(T) do
|
||||||
|
if name == aName then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- save a lua table to file
|
||||||
|
function tcli.saveLuaTable(path, theTable, theName)
|
||||||
|
local theFile = nil
|
||||||
|
theFile = io.open(path, "w") -- overwrite
|
||||||
|
tcli.writeTable(theFile, theName, theTable)
|
||||||
|
theFile:write("\n-- tickled by tcli")
|
||||||
|
theFile:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.writeTable(theFile, key, value, prefix, inrecursion)
|
||||||
|
local comma = ""
|
||||||
|
if inrecursion then
|
||||||
|
if tonumber(key) then key = '[' .. key .. ']' else key = '["' .. key .. '"]' end
|
||||||
|
comma = ","
|
||||||
|
end
|
||||||
|
if not value then value = false end -- not NIL!
|
||||||
|
if not prefix then prefix = "" else prefix = "\t" .. prefix end
|
||||||
|
if type(value) == "table" then -- recursively write a table
|
||||||
|
theFile:write(prefix .. key .. " = \n" .. prefix .. "{\n")
|
||||||
|
for k,v in pairs (value) do -- iterate all kvp
|
||||||
|
tcli.writeTable(theFile, k, v, prefix, true)
|
||||||
|
end
|
||||||
|
theFile:write(prefix .. "}" .. comma .. " -- end of " .. key .. "\n")
|
||||||
|
elseif type(value) == "boolean" then
|
||||||
|
local b = "false"
|
||||||
|
if value then b = "true" end
|
||||||
|
theFile:write(prefix .. key .. " = " .. b .. comma .. "\n")
|
||||||
|
elseif type(value) == "string" then -- quoted string, WITH proccing
|
||||||
|
value = string.gsub(value, "\\", "\\\\") -- escape "\" to "\\", others ignored, possibly conflict with \n
|
||||||
|
value = string.gsub(value, string.char(10), "\\" .. string.char(10)) -- 0A --> "\"0A
|
||||||
|
theFile:write(prefix .. key .. ' = "' .. value .. '"' .. comma .. "\n")
|
||||||
|
else -- simple var, show contents, ends recursion
|
||||||
|
theFile:write(prefix .. key .. " = " .. value .. comma .. "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CLI for admins --
|
||||||
|
function tcli.adminCall(playerID, line) -- returns string
|
||||||
|
-- break line into space-delimited commands
|
||||||
|
local cmd = {}
|
||||||
|
local sep = " "
|
||||||
|
for str in string.gmatch(line, "([^"..sep.."]+)") do
|
||||||
|
table.insert(cmd, str)
|
||||||
|
end
|
||||||
|
if #cmd < 1 then return "adm: input error" end
|
||||||
|
local c = cmd[1]
|
||||||
|
if c then c = string.upper(c) end
|
||||||
|
if c == "?" then return tcli.help() end
|
||||||
|
if c == "NEXT" then return tcli.nextMission() end
|
||||||
|
if c == "PREV" or c == "PREVIOUS" then return tcli.previousMission() end
|
||||||
|
if c == "RANDOM" then return tcli.randomMission() end
|
||||||
|
if c == "RESTART" then return tcli.restartMission() end
|
||||||
|
if c == "PAUSE" then return tcli.pauseMission(true) end
|
||||||
|
if c == "PLAY" then return tcli.pauseMission(false) end
|
||||||
|
if c == "CYCLETIME" then return tcli.cycleTime(cmd[2]) end
|
||||||
|
if c == "SHUFFLE" then return tcli.shuffle() end
|
||||||
|
if c == "SEQ" or c == "SEQUENCE" then return tcli.unshuffle() end
|
||||||
|
if c == "CYCLE" then return tcli.cycleNow() end
|
||||||
|
return "cli: unknown command <" .. c .. ">"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.help()
|
||||||
|
local cycleStatus = "Disabled automatic mission change"
|
||||||
|
if not tcli.config.cycleTime then tcli.config.cycleTime = -1 end
|
||||||
|
if tcli.config.cycleTime >= 1 then
|
||||||
|
local now = DCS.getModelTime()
|
||||||
|
if not tcli.endTime then tcli.endTime = 99999999 end
|
||||||
|
local remains = tcli.endTime - now
|
||||||
|
cycleStatus = "AUTOMATIC MISSION CHANGE EVERY " .. tcli.config.cycleTime .. " MINUTES, " .. tcli.num2ms(remains) .. " MMM:SS remaining"
|
||||||
|
end
|
||||||
|
if tcli.cfg.listShuffle then cycleStatus = cycleStatus .. ", shuffle ON" else cycleStatus = cycleStatus .. ", NO shuffle" end
|
||||||
|
local s = "CLI v" .. tcli.version .. ": -? (help), -next, -previous, -restart, -random, -cycle, -pause, -play, -cycleTime, -shuffle, -sequence"
|
||||||
|
s = s .. " " .. cycleStatus
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.getMsnIndex()
|
||||||
|
local curr = DCS.getMissionFilename( ) -- gets full path
|
||||||
|
for x, msn in pairs(tcli.cfg.missionList) do
|
||||||
|
if msn == curr then return x end
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.nextMission() -- automatically loops if on last
|
||||||
|
if #tcli.cfg.missionList < 2 then
|
||||||
|
return xli.restartMission()
|
||||||
|
end
|
||||||
|
local idx = tcli.getMsnIndex() + 1
|
||||||
|
if idx > #tcli.cfg.missionList then idx = 1 end
|
||||||
|
local new = tcli.cfg.missionList[idx]
|
||||||
|
tcli.cfg.listStartIndex = idx
|
||||||
|
tcli.cfg.lastSelectedMission = new
|
||||||
|
tcli.saveLuaTable(tcli.serverCfgPath, tcli.cfg, "cfg")
|
||||||
|
net.log("tcli: Starting next mission in sequence: <" .. new .. ">.")
|
||||||
|
net.load_mission(new)
|
||||||
|
return "Loading next mission (" .. new .. ")."
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.previousMission() -- automatically loops if on first
|
||||||
|
if #tcli.cfg.missionList < 2 then
|
||||||
|
return xli.restartMission()
|
||||||
|
end
|
||||||
|
local idx = tcli.getMsnIndex() - 1
|
||||||
|
if idx < 1 then idx = #tcli.cfg.missionList end
|
||||||
|
local new = tcli.cfg.missionList[idx]
|
||||||
|
tcli.cfg.listStartIndex = idx
|
||||||
|
tcli.cfg.lastSelectedMission = new
|
||||||
|
tcli.saveLuaTable(tcli.serverCfgPath, tcli.cfg, "cfg")
|
||||||
|
net.log("tcli: Starting previous mission in sequence: <" .. new .. ">.")
|
||||||
|
net.load_mission(new)
|
||||||
|
return "Loading previous mission (" .. new .. ")."
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.restartMission()
|
||||||
|
local curr = DCS.getMissionFilename()
|
||||||
|
net.load_mission(curr)
|
||||||
|
return "re-starting mission (" .. curr .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.randomMission()
|
||||||
|
if #tcli.cfg.missionList < 2 then return xli.restartMission() end
|
||||||
|
local curr = DCS.getMissionFilename() -- gets full path
|
||||||
|
local count = 0
|
||||||
|
local new
|
||||||
|
local pick
|
||||||
|
repeat
|
||||||
|
pick = math.random(1, #tcli.cfg.missionList)
|
||||||
|
new = tcli.cfg.missionList[pick]
|
||||||
|
count = count + 1
|
||||||
|
until (count > 20) or (new ~= curr)
|
||||||
|
if count > 20 then return "mission picker error" end
|
||||||
|
tcli.cfg.listStartIndex = pick
|
||||||
|
tcli.cfg.lastSelectedMission = new
|
||||||
|
tcli.saveLuaTable(tcli.serverCfgPath, tcli.cfg, "cfg")
|
||||||
|
net.log("tcli: Starting random mission: <" .. new .. ">.")
|
||||||
|
net.load_mission(new)
|
||||||
|
return "Starting random mission: " .. new
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.pauseMission(doPause)
|
||||||
|
DCS.setPause(doPause)
|
||||||
|
if doPause then return "Pausing Mission" end
|
||||||
|
return "Mission continues"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.num2ms(num)
|
||||||
|
mins = math.floor(num / 60)
|
||||||
|
sec = math.floor(num%60)
|
||||||
|
return string.format("%03d", mins) .. ":" .. string.format("%02d", sec)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.cycleTime(param)
|
||||||
|
if not param then
|
||||||
|
if tcli.config.cycleTime and tcli.config.cycleTime >= 1 then
|
||||||
|
local now = DCS.getModelTime()
|
||||||
|
local remains = tcli.endTime - now
|
||||||
|
return "AUTOMATIC MISSION CHANGE AFTER " .. tcli.config.cycleTime .. " MINUTES -- now scheduled in " .. tcli.num2ms(remains) .. " MMM:SS"
|
||||||
|
end
|
||||||
|
return "DIASBLED automatic mission change"
|
||||||
|
end
|
||||||
|
local num = tonumber(param)
|
||||||
|
if not num then num = -1 end
|
||||||
|
tcli.config.cycleTime = num
|
||||||
|
tcli.saveData(tcli.myConfig, tcli.config)
|
||||||
|
if num >= 1 then tcli.endTime = tcli.config.cycleTime * 60
|
||||||
|
else tcli.endTime = 0 end
|
||||||
|
if num >= 1 then return "ENABLED automatic mission change after " .. tcli.config.cycleTime .. " minutes" end
|
||||||
|
return "Turned OFF automatic mission change"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.shuffle()
|
||||||
|
tcli.cfg.listShuffle = true
|
||||||
|
tcli.saveLuaTable(tcli.serverCfgPath, tcli.cfg, "cfg")
|
||||||
|
return "Mission order is now randomized (shuffled)"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.unshuffle()
|
||||||
|
tcli.cfg.listShuffle = false
|
||||||
|
tcli.saveLuaTable(tcli.serverCfgPath, tcli.cfg, "cfg")
|
||||||
|
return "Missions now play in sequence"
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.cycleNow()
|
||||||
|
if tcli.cfg.listShuffle then -- randomized playlist
|
||||||
|
tcli.randomMission()
|
||||||
|
return "Immediately cyling to random mission."
|
||||||
|
end -- next, will loop
|
||||||
|
tcli.nextMission()
|
||||||
|
return "Immediately cycling to next mission."
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- CLI MAIN ENTRY, command in message
|
||||||
|
--
|
||||||
|
function tcli.onPlayerTrySendChat(playerID, message, all )
|
||||||
|
if not DCS.isServer() then return end
|
||||||
|
if not DCS.isMultiplayer() then return end
|
||||||
|
local name = net.get_player_info(playerID, 'name')
|
||||||
|
-- check to see if message starts with cli mark
|
||||||
|
local i, j = string.find(message, tcli.config.mark, 1, true)
|
||||||
|
if i == 1 then -- line starts with cli prompt
|
||||||
|
if tcli.nameInTable(name, tcli.config.admins) then
|
||||||
|
message = message:sub(1 + #tcli.config.mark)
|
||||||
|
local msg = tcli.adminCall(playerID, message)
|
||||||
|
net.send_chat_to(msg, playerID) -- player only
|
||||||
|
return "" -- while line output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.getServerConfig()
|
||||||
|
-- load/update server config into cfg
|
||||||
|
local s = tcli.loadFile(tcli.serverCfgPath)
|
||||||
|
net.log("tcli: loaded server config file: " .. s)
|
||||||
|
cfg = nil -- nil before loadString
|
||||||
|
f = loadstring(s)
|
||||||
|
f() -- define conf so we have access to serverconfig
|
||||||
|
if cfg then tcli.cfg = cfg end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.onMissionLoadBegin() -- reload to avoid DCS restart
|
||||||
|
if not DCS.isServer() then return end
|
||||||
|
if not DCS.isMultiplayer() then return end
|
||||||
|
tcli.getServerConfig() -- update current list of missions
|
||||||
|
tcli.lastTime = 0
|
||||||
|
tcli.hasWarned5 = false
|
||||||
|
tcli.hasWarned1 = false
|
||||||
|
tcli.endTime = 99999999 -- very, very late
|
||||||
|
local d = tcli.loadData(tcli.myConfig) -- update config
|
||||||
|
if d then tcli.config = d end
|
||||||
|
if tcli.config.cycleTime and tcli.config.cycleTime >= 1 then
|
||||||
|
tcli.endTime = tcli.config.cycleTime * 60 -- in minutes!
|
||||||
|
end
|
||||||
|
net.log("tcli: Mission <" .. DCS.getMissionName() .. ">: Mission Load Begin - Inited tcli.endTime to <" .. tcli.endTime .. ">.")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.update()
|
||||||
|
local now = DCS.getModelTime()
|
||||||
|
-- if cycle time is enabled, we check if we need to advance the Mission
|
||||||
|
if tcli.config.cycleTime and tcli.config.cycleTime >= 1 then
|
||||||
|
local remains = tcli.endTime - now
|
||||||
|
-- warning broadcasts
|
||||||
|
if not tcli.hasWarned5 and remains < 5 * 60 then
|
||||||
|
net.send_chat("THIS MISSION ENDS IN 5 MINUTES", true)
|
||||||
|
tcli.hasWarned5 = true
|
||||||
|
end
|
||||||
|
if not tcli.hasWarned1 and remains < 60 then
|
||||||
|
net.send_chat("THIS MISSION ENDS IN 1 MINUTE", true)
|
||||||
|
tcli.hasWarned1 = true
|
||||||
|
end
|
||||||
|
if remains < 0 then
|
||||||
|
if tcli.cfg.listShuffle then -- randomized playlist
|
||||||
|
tcli.randomMission()
|
||||||
|
else -- next, will loop
|
||||||
|
tcli.nextMission()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tcli.onSimulationFrame()
|
||||||
|
if not DCS.isServer() then return end
|
||||||
|
if not DCS.isMultiplayer() then return end
|
||||||
|
-- every 50 seconds we do an update. not during pause!
|
||||||
|
if tcli.lastTime + 50 < DCS.getModelTime() then
|
||||||
|
tcli.update()
|
||||||
|
tcli.lastTime = DCS.getModelTime()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start up
|
||||||
|
if tcli.hasFile(tcli.myConfig) then
|
||||||
|
local d = tcli.loadData(tcli.myConfig)
|
||||||
|
if d then
|
||||||
|
tcli.config = d
|
||||||
|
net.log("tcli: successfuly read existing config file")
|
||||||
|
else
|
||||||
|
net.log("tcli: ERROR LOADING CONFIG FILE. DELETE AND TRY AGAIN.")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tcli.saveData(tcli.myConfig, tcli.config)
|
||||||
|
net.log("tcli: created new tcli config file.")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hook into dcs server
|
||||||
|
DCS.setUserCallbacks(tcli)
|
||||||
Loading…
x
Reference in New Issue
Block a user