Skip to content

Instantly share code, notes, and snippets.

@jboecker
Created February 26, 2015 16:49
Show Gist options
  • Save jboecker/111aa4cf2bdff4c16329 to your computer and use it in GitHub Desktop.
Save jboecker/111aa4cf2bdff4c16329 to your computer and use it in GitHub Desktop.
DCS Witchcraft adapted to the DCS "server" Lua environment (e.g. by commenting out env.info() calls)
-- Server hooks
module('server', package.seeall)
-- do not load real gettext here
-- _ only marks strings for translation
local _ = function(msg) return msg end
local log
local names = {}
local function log_write(str)
net.log(str)
if log then log:write(os.date("%c") .. " : " .. str .. "\n") end
end
local function unit_type(unit) return net.get_unit_property(unit, 4) or "" end
local function side_name(side)
if side == 0 then return "Spectators"
elseif side == 1 then return "Red"
else return "Blue" end
end
function on_net_start()
--log = io.open(lfs.writedir() .. "Logs/net-server-"..os.date("%Y%m%d-%H%M%S")..".log", "w")
log = io.open(lfs.writedir() .. "Logs/net-server.log", "w")
log_write("Server started")
names = {}
names[net.get_name(1)] = 1
end
function on_mission(filename)
--already reported
--log_write("Loaded mission ", filename)
-- parse available slots
local serializer = [[
serialize = function(val)
if type(val)=='number' or type(val)=='boolean' then
return tostring(val)
elseif type(val)=='string' then
return string.format("%q", val)
elseif type(val)=='table' then
local k,v
local str = '{'
for k,v in pairs(val) do
str=str..'['..serialize(k)..']='..serialize(v)..','
end
str = str..'}'
return str
end
return 'nil'
end
]]
-- load serializer into mission env
net.dostring_in('mission', serializer)
-- parse available slots
local slot_parser = [[
local side_parser = function(side)
local i,v
local slots = {}
for i,v in ipairs(side) do
local u = { unit_id = v.unitId, type = v.type, onboard_num = v.onboard_num }
local group = v.group
if group then
u.group_name = group.name
u.group_task = group.task
u.country_name = group.country.name
end
table.insert(slots, u)
end
return slots
end
local res = { red = side_parser(db.clients.red), blue = side_parser(db.clients.blue) }
return serialize(res)
]]
local val, res = net.dostring_in('mission', slot_parser)
--net.log(string.format("%s: (%s) %q", 'mission', tostring(res), val))
if res then
local t = loadstring('return '..val)
game.slots = t()
else
game.slots = {}
end
end
function on_net_stop()
log_write("Server stopped")
if log then
log:close()
log = nil
end
names = {}
end
function on_process()
end
function on_connect(id, addr, port, name, ucid)
--[[ banning example
if banned_hosts and banned_hosts[addr] then
-- extend the ban
--banned_hosts[addr] = os.time()
return "Banned by IP", false
end
if banned_names and banned_names[name] then
-- extend the ban
--banned_names[name] = os.time()
return "Banned by name", false
end
if banned_serials and banned_serials[ucid] then
-- extend the ban
--banned_names[name] = os.time()
return "Banned by UniqueClientID", false
end
]]
-- write to log
log_write(string.format("Connected client: id = [%d], addr = %s:%d, name = %q, ucid = %q",
id, addr, port, name, ucid))
net.recv_chat(string.format("Connected client: id = [%d], addr = %s:%d, name = %q, ucid = %q", id, addr, port, name, ucid))
if names[name] then
return _("Please, provide a unique nickname."), false
end
names[name] = id
return true
end
function on_disconnect(id, err)
local n = net.get_name(id)
if names[n] then
names[n] = nil
end
log_write(string.format("Disconnected client [%d] %q", id, n or ""))
end
--
function on_set_name(id, new_name)
-- check against ban list
--if banned_names[new_name] then
-- kick(id, "banned name")
--end
old_name = net.get_name(id)
if names[new_name] then
log_write(string.format("Client [%d] %q tried to changed name to %q", id, old_name, new_name))
return old_name
end
names[old_name] = nil
names[new_name] = id
log_write(string.format("Client [%d] %q changed name to %q", id, old_name, new_name))
return name
end
function on_set_unit(id, side, unit)
name = net.get_name(id)
if unit ~= "" then
msg = string.format("Client [%d] %q joined %s in %q(%s)", id, name, side_name(side), unit_type(unit), unit)
else
msg = string.format("Client [%d] %q joined %s", id, name, side_name(side))
end
log_write(msg)
return true
end
function on_chat(id, msg, all)
if msg=="/mybad" then
return string.format("I (%d, %q) has made a screenshot at %f", id, net.get_name(id), net.get_model_time())
elseif string.sub(msg, 1, 1) == '/' then
net.recv_chat("got command: "..msg, 0)
return
end
return msg
end
--------------------------------------------------
-- load event callbacks
dofile('./Scripts/net/events.lua')
dofile(lfs.writedir() .. 'Scripts/net/WitchcraftServerEnv.lua')
--[[
Setup of on_process() function taken from the excellent example set by SLMod
]] --
log("Loading Witchcraft into the server environment...")
witchcraft = witchcraft or {}
witchcraft.oldfunc = {}
witchcraft.oldfunc.on_process = server.on_process()
do
witchcraft.host = "localhost"
witchcraft.port = 3001
local require = require
local loadfile = loadfile
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.step(arg, time)
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
end
end
witchcraft.start = function(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)
witchcraft.scheduled = true
end
end
witchcraft.log = function(data)
local msg = { ["type"] = "log", ["data"] = data };
witchcraft.txbuf = witchcraft.txbuf .. JSON:encode(msg):gsub("\n","").."\n"
end
function server.on_process()
if not witchcraft.started then
witchcraft.start(_G)
witchcraft.started = true
end
witchcraft.step(nil, 0)
end
end
log("Witchcraft loaded")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment