--[[ Add the following to MissionScripting.lua: witchcraft = {} witchcraft.host = "localhost" witchcraft.port = 3001 dofile(lfs.writedir().."Scripts\\witchcraft.lua") ]]-- do local require = require local loadfile = loadfile env.info("initializing witchcraft...") if not witchcraft then env.info("global witchcraft object does not exist. Did you apply the required changes to MissionScripting.lua?") return end package.path = package.path..";.\\LuaSocket\\?.lua" package.cpath = package.cpath..";.\\LuaSocket\\?.dll" local JSON = loadfile("Scripts\\JSON.lua")() witchcraft.JSON = JSON local socket = require("socket") function witchcraft.luaMissionToJSONable() local mission = mist.utils.deepCopy(env.mission) for _, coalition in pairs(mission.coalition) do for _, country in pairs(coalition.country) do for _, category_name in pairs({"vehicle", "helicopter", "plane", "ship"}) do local category = country[category_name] if category then for _, group in pairs(category.group) do for _, unit in pairs(group.units) do if type(unit.callsign) == "table" then local new_callsign = { name = unit.callsign.name, ["1"] = unit.callsign[1], ["2"] = unit.callsign[2], ["3"] = unit.callsign[3] } unit.callsign = new_callsign end end end end end end end return mission end function witchcraft.JSONableMissionToLua(jsonMission) local mission = mist.utils.deepCopy(jsonMission) for _, coalition in pairs(mission.coalition) do for _, country in pairs(coalition.country) do for _, category_name in pairs({"vehicle", "helicopter", "plane", "ship"}) do local category = country[category_name] if category then for _, group in pairs(category.group) do group.x = group.units[1].x group.y = group.units[1].y group.route.points[1].x = group.units[1].x group.route.points[1].y = group.units[1].y if group.route.spans and group.route.spans[1] then group.route.spans[1][1].x = group.units[1].x group.route.spans[1][1].y = group.units[1].y end for _, unit in pairs(group.units) do if type(unit.callsign) == "table" then local new_callsign = { name = unit.callsign.name, [1] = unit.callsign["1"], [2] = unit.callsign["2"], [3] = unit.callsign["3"] } unit.callsign = new_callsign end end end end end end end return mission end function witchcraft.considerUnit(unit) if not unit then return false end if not Unit.isExist(unit) then return false end if not unit:isActive() then return false end return true end function witchcraft.unitUpdate() msg = {} msg.type = "unitupdate" msg.units = {} for _, aliveUnit in pairs(mist.DBs.aliveUnits) do local unit = Unit.getByName(aliveUnit.unitName) if witchcraft.considerUnit(unit) then local pos = unit:getPosition() msg.units[#msg.units+1] = { uN = aliveUnit.unitName, pd = pos, -- position_dcs cat = aliveUnit.category, t = unit:getTypeName(), c = unit:getCoalition(), alt = pos.p.y - land.getHeight({ x = pos.p.x, y = pos.p.z}), } end end witchcraft.txbuf = JSON:encode(msg):gsub("\n", "").."\n" end witchcraft.groupNamesToSetInvisible = {} witchcraft.syncJSONGroup = function(group) for _, unit in pairs(group.units) do if type(unit.callsign) == "table" then local new_callsign = { name = unit.callsign.name, [1] = unit.callsign["1"], [2] = unit.callsign["2"], [3] = unit.callsign["3"] } unit.callsign = new_callsign end end group.route = {} if group.category == "static" then mist.dynAddStatic(group) else mist.dynAdd(group) end witchcraft.groupNamesToSetInvisible[#witchcraft.groupNamesToSetInvisible+1] = group.name end function witchcraft.step(arg, time) for _, groupName in pairs(witchcraft.groupNamesToSetInvisible) do local grp = Group.getByName(groupName) if grp and grp:isExist() then grp:getController():setCommand({id="SetInvisible", params={value=true}}) end end witchcraft.groupIdsToSetInvisible = {} if witchcraft.txbuf:len() == 0 then if witchcraft.unitUpdateInterval > 0 then if (timer.getTime() - witchcraft.lastUnitUpdateTime > witchcraft.unitUpdateInterval) or (witchcraft.lastUnitUpdateTime == 0) then local bool, err = pcall(witchcraft.unitUpdate) if not bool then env.info("witchcraft.unitUpdate() failed: "..err) end witchcraft.lastUnitUpdateTime = timer.getTime() end end end witchcraft.txbuf = witchcraft.txbuf .. '{"type":"dummy"}\n' if witchcraft.txbuf:len() > 0 then local bytes_sent = nil local ret1, ret2, ret3 = witchcraft.conn:send(witchcraft.txbuf) if ret1 then bytes_sent = ret1 else env.info("could not send witchcraft: "..ret2) if ret3 == 0 then if ret2 == "closed" then witchcraft.txbuf = '{"type":"dummy"}\n' witchcraft.rxbuf = "" witchcraft.lastUnitUpdateTime = 0 witchcraft.conn = socket.tcp() witchcraft.conn:settimeout(.0001) env.info("witchcraft: socket was closed") end env.info("reconnecting to "..tostring(witchcraft.host)..":"..tostring(witchcraft.port)) witchcraft.conn:connect(witchcraft.host, witchcraft.port) return end bytes_sent = ret3 end witchcraft.txbuf = witchcraft.txbuf:sub(bytes_sent + 1) else if witchcraft.txidle_hook then local bool, err = pcall(witchcraft.txidle_hook) if not bool then env.info("witchcraft.txidle_hook() failed: "..err) end end end local line, err = witchcraft.conn:receive() if err then env.info("witchcraft read error: "..err) else msg = JSON:decode(line) if msg.type == "lua" then local response_msg = {} response_msg.type = "luaresult" response_msg.name = msg.name local f, error_msg = loadstring(msg.code, msg.name) if f then witchcraft.context = {} witchcraft.context.arg = msg.arg setfenv(f, witchcraft.mission_env) response_msg.success, response_msg.result = pcall(f) else response_msg.success = false response_msg.result = tostring(error_msg) end local response_string = "" local function encode_response() response_string = JSON:encode(response_msg):gsub("\n","").."\n" end local success, result = pcall(encode_response) if not success then response_msg.success = false response_msg.result = tostring(result) encode_response() end witchcraft.txbuf = witchcraft.txbuf .. response_string end if msg.type == "smoke" then local smoke_color = msg.color or "Green" local center = coord.LLtoLO(msg.lat, msg.lon, 0) center.y = land.getHeight({ x = center.x, y = center.z}) trigger.action.smoke(center, trigger.smokeColor[smoke_color]) local response_msg = {} response_msg.type = "smokeconfirm" response_msg.lon = msg.lon response_msg.lat = msg.lat response_msg.color = smoke_color witchcraft.txbuf = witchcraft.txbuf .. JSON:encode(response_msg):gsub("\n","").."\n" end if msg.type == "set-unit-ai-visibility" then for unitname, visible in pairs(msg.units) do local unit = Unit.getByName(unitname) if unit and unit:isExist() then unit:getController():setCommand({id="SetInvisible", params={value=(not visible)}}) else env.info("unit.getByName failed for "..unitname) end end end end end function witchcraft_start(mission_env_) witchcraft.mission_env = mission_env_ if not witchcraft.scheduled then witchcraft.lastUnitUpdateTime = 0 witchcraft.unitUpdateInterval = 0 witchcraft.txbuf = '{"type":"dummy"}\n' witchcraft.rxbuf = "" witchcraft.lastUnitUpdateTime = 0 witchcraft.conn = socket.tcp() witchcraft.conn:settimeout(.0001) witchcraft.conn:connect(witchcraft.host, witchcraft.port) timer.scheduleFunction(function(arg, time) local bool, err = pcall(witchcraft.step) if not bool then env.info("witchcraft.step() failed: "..err) end return timer.getTime() + .1 -- <<< update interval end, nil, timer.getTime() + .1) witchcraft.scheduled = true end end witchcraft.start = witchcraft_start function witchcraft_log(data) local msg = { ["type"] = "log", ["data"] = data }; witchcraft.txbuf = witchcraft.txbuf .. JSON:encode(msg):gsub("\n","").."\n" end witchcraft.log = witchcraft_log end