diff --git a/01_DCS/Mods/services/Perun/lua/perun_config.lua b/01_DCS/Mods/services/Perun/lua/pierog_config.lua similarity index 63% rename from 01_DCS/Mods/services/Perun/lua/perun_config.lua rename to 01_DCS/Mods/services/Perun/lua/pierog_config.lua index 57139a4..c065f44 100644 --- a/01_DCS/Mods/services/Perun/lua/perun_config.lua +++ b/01_DCS/Mods/services/Perun/lua/pierog_config.lua @@ -5,24 +5,29 @@ local config = {} -- Connection config.host = "localhost" -- (string) [default: "localhost"] IP adress of the Perun instance or "localhost" config.port = 48621 -- (int) [default: 48621] TCP port to send data to +config.delimiters = {"", ""} config.Instance = 1 -- (int) [default: 1] Id number of instance (if multiple DCS instances run on the same machine) config.keepAliveSeconds = 5 -- -config.refreshSlotsSeconds = 300 -- delay between slot updates are sent -config.refreshMissionSeconds = 600 -- delay between mission update is sent +config.logPath = "c:\\temp" -- path for communication logs + config.bigUpdatesSeconds = 100 -- big updates (mission, unit IDs, slots) have to wait between being sent +-- deprecated, decrease greatly +config.refreshSlotsSeconds = 300 -- delay between slot updates are sent +config.refreshMissionSeconds = 200 -- delay between mission update is sent -- Localisation -- Server MOTD, sent to ALL players when entering cockpit -config.MOTD_line1 = "[Perun] Welcome to our server!"; -config.MOTD_line2 = "[Perun] Stats and event data integrated with Perun for DCS World"; +config.MOTD_line1 = "[Pierog] Welcome to our server!"; +config.MOTD_line2 = "[Pierog] Stats and event data integrated with Pierog for DCS World"; -- (string) Information to send to players when Perun connection is broken -config.ConnectionError = "[Perun] ERROR: Connection broken - contact server admin!" +config.ConnectionError = "[Pierog] ERROR: Connection broken - contact server admin!" -- Misc -config.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun +config.BroadcastPierogErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun +config.BroadcastErrorEachSeconds = 600 -- Debug -config.DebugMode = 1 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged +config.DebugMode = 2 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged -- ###################### END OF SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION ###################### return config diff --git a/01_DCS/Scripts/Hooks/Perun_light_hook.lua b/01_DCS/Scripts/Hooks/Perun_light_hook.lua deleted file mode 100644 index 0dc9e3c..0000000 --- a/01_DCS/Scripts/Hooks/Perun_light_hook.lua +++ /dev/null @@ -1,480 +0,0 @@ --- Perun for DCS World https://github.com/szporwolik/perun -> DCS Hook component -net.log("[Perun] Loading Perun") - --- Initial init -local Perun = {} - --- Load Luas -package.path = package.path..";"..lfs.currentdir().."/LuaSocket/?.lua"..";"..lfs.writedir() .. "/Mods/services/Perun/lua/?.lua" -package.cpath = package.cpath..";"..lfs.currentdir().."/LuaSocket/?.dll" - --- Load Dlls -package.cpath = package.cpath..';'.. lfs.writedir()..'/Mods/services/Perun/bin/' ..'?.dll;' -net.log("[Perun]", "Loading DLL") -Perun.DLL = require('perun') -net.log("[Perun]", "Loaded DLL, starting") -Perun.DLL.StartOfApp(); -net.log("[Perun]", "DLL started") - --- Load config file -net.log("[Perun]", "Loading config") -local config = require "perun_config" -net.log("[Perun]", "Config loaded") - -Perun.host = config.host -Perun.port = config.port -Perun.Instance = config.Instance -Perun.DebugMode = config.DebugMode - -Perun.MOTD_line1 = config.MOTD_line1 -Perun.MOTD_line2 = config.MOTD_line2 -Perun.ConnectionError = config.ConnectionError -Perun.BroadcastPerunErrors = config.BroadcastPerunErrors - -Perun.keepAliveSeconds = config.keepAliveSeconds -Perun.bigUpdatesSeconds = config.bigUpdatesSeconds -Perun.refreshSlotsSeconds = config.refreshSlotsSeconds -Perun.refreshMissionSeconds = config.refreshMissionSeconds - -Perun.Version = "v0.12.1" - -Perun.MissionHash="" - -Perun.lastSentStatus = 0 -Perun.lastSentMission = 0 -Perun.lastSentKeepAlive = 0 -Perun.lastConnectionError = 0 -Perun.lastFrameStart = 0; -Perun.lastTimer = 0 -Perun.lastFrameTime = 0; - -Perun.ReconnectTimeout = 30; -Perun.RefreshKeepAlive = 5 - -Perun.State = {} -Perun.State.connected = false; -Perun.State.slotsInitialised = false; - --- to avoid declaring every time a function is run -Perun.Globals = {} -Perun.Globals.Sides = { - [0] = 'SPECTATOR', - [1] = 'RED', - [2] = 'BLUE', - [3] = 'NEUTRAL', -- TBD check once this is released in DCS -} - -Perun.Mission = {} -Perun.Mission.Slots = {} -Perun.Mission.Slots["blue"] = {} -Perun.Mission.Slots["red"] = {} -Perun.Mission.Players = {} -Perun.Mission.Units = {} - -Perun.HandlerArgCounts = {} - -Perun.HandlerArgCounts["self_kill"] = 1 -Perun.HandlerArgCounts["crash"] = 2 -Perun.HandlerArgCounts["eject"] = 2 -Perun.HandlerArgCounts["connect"] = 2 -Perun.HandlerArgCounts["mission_end"] = 2 -Perun.HandlerArgCounts["pilot_death"] = 2 -Perun.HandlerArgCounts["takeoff"] = 3 -Perun.HandlerArgCounts["landing"] = 3 -Perun.HandlerArgCounts["change_slot"] = 3 -Perun.HandlerArgCounts["friendly_fire"] = 3 -Perun.HandlerArgCounts["disconnect"] = 4 -Perun.HandlerArgCounts["kill"] = 7 - -net.log("[Perun]", "Global variables initialised") - -Perun.Stats = {} -Perun.Stats.LastSentTCP = 0 -Perun.Stats.LastSentSlots = 0 -Perun.Stats.LastBigUpdate = 0 -Perun.Stats.LastSentMission = 0 -Perun.Stats.PreviousFrameStart = 0 -Perun.Stats.LastConnectionError = 0 - -Perun.func = {}; -Perun.func.ensureSlots = function() - if(not Perun.State.slotsInitialised) then - net.log("Perun", "initialising slots") - - local blue = DCS.getAvailableSlots("blue"); - for k, unit in pairs(blue) do - Perun.Mission.Slots["blue"][unit["unitId"]] = unit - end - - local red = DCS.getAvailableSlots("red"); - for k, unit in pairs(red) do - Perun.Mission.Slots["red"][unit["unitId"]] = unit - end - - Perun.State.slotsInitialised = true; - end -end - -Perun.func.ensureUnitId = function(unitId) - if(not Perun.Mission.Units[unitId]) then - local unitData = DCS.getUnitType(unitId) - Perun.Mission.Units[unitId] = unitData - local payload = {} - payload["unit"] = unitData - - Perun.func.sendData(net.lua2json(unitData)) - end -end - -Perun.func.sendData = function(payload) - - local now = DCS.getRealTime() - - local connected, reconnected = Perun.DLL.tcpSend(payload) - net.log("[Perun]", connected, reconnected) - if(connected < 1) and (now > Perun.Stats.LastConnectionError + Perun.ReconnectTimeout) then - Perun.AddLog("ERROR - TCP connection is not available",0) - - if Perun.BroadcastPerunErrors > 0 then - local _all_players = net.get_player_list() - for i, playerId in ipairs(_all_players) do - net.send_chat_to(Perun.ConnectionError , playerId) - end - end - - Perun.Stats.LastConnectionError = now - else - Perun.Stats.LastSentTCP = now - - if(reconnected > 0) then - net.log("Perun", "TCP reconnected") - Perun.Stats.LastBigUpdate = 0 - Perun.Stats.LastSentSlots = 0 - Perun.Stats.LastSentMission = 0 - end - end -end - -Perun.Handlers = {} -Perun.Handlers["event"] = function(eventName) - local data = {}; - data["event"] = eventName; - data["hash"] = Perun.MissionHash - data['datetime']=os.date('%Y-%m-%d %H:%M:%S') - - return data; -end - -Perun.Handlers["friendly_fire"] = function(playerID, weaponName, victimPlayerID) - local data = Perun.Handlers["event"]("friendly_fire") - - data["killer"] = playerID; - data["victim"] = victimPlayerID; - data["weapon"] = weaponName; - - return data; -end - -Perun.Handlers["mission_end"] = function(winner, msg) - local data = Perun.Handlers["event"]("mission_end") - - data["winner"] = winner; - data["message"] = msg; - - return data; -end - -Perun.Handlers["kill"] = function(killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName) - local data = Perun.Handlers["event"]("kill") - - data["killer"] = {killerPlayerID, killerUnitType, killerSide} - data["victim"] = {victimPlayerID, victimUnitType, victimSide} - data["weapon"] = weaponName; - - return data; -end - -Perun.Handlers["self_kill"] = function(playerID) - local data = Perun.Handlers["event"]("self_kill") - data["who"] = playerID; - - return data; -end - -Perun.Handlers["change_slot"] = function(playerID, slotID, prevSide) - local details = net.get_player_info(playerID, "side") - Perun.Mission.Players[playerID]["side"] = details - - local data = Perun.Handlers["event"]("change_slot") - data["previousSlot"] = slotID - data["previousSide"] = prevSide - - return data; -end - -Perun.Handlers["connect"] = function(playerID, name) - net.log("Perun", "Getting player details") - local details = net.get_player_info(playerID) - Perun.Mission.Players[playerID] = details - - net.log("Perun", "Preparing event") - local data = Perun.Handlers["event"]("connect") - data["who"] = playerID - data["details"] = details; - - return data; -end - -Perun.Handlers["disconnect"] = function(playerID, name, playerSide, reason_code) - local data = Perun.Handlers["event"]("disconnect") - data["who"] = playerID; - data["details"] = {name, playerSide, reason_code} - - Perun.Mission.Players[playerID] = nil - - return data; -end ---"crash", playerID, unit_missionID -Perun.Handlers["crash"] = function(playerID, unit_missionID) - Perun.func.ensureUnitId(unit_missionID) - - local data = Perun.Handlers["event"]("crash") - data["who"] = playerID; - data["unitId"] = unit_missionID; - - return data; -end ---"eject", playerID, unit_missionID -Perun.Handlers["eject"] = function(playerID, unit_missionID) - Perun.func.ensureUnitId(unit_missionID) - - local data = Perun.Handlers["event"]("eject") - data["who"] = playerID; - data["unitId"] = unit_missionID; - - return data; -end ---"takeoff", playerID, unit_missionID, airdromeName -Perun.Handlers["takeoff"] = function(playerID, unit_missionID, airdromeName) - Perun.func.ensureUnitId(unit_missionID) - - local data = Perun.Handlers["event"]("takeoff") - data["who"] = playerID; - data["unitId"] = unit_missionID; - data["from"] = airdromeName; - - return data; -end ---"landing", playerID, unit_missionID, airdromeName -Perun.Handlers["landing"] = function(playerID, unit_missionID, airdromeName) - Perun.func.ensureUnitId(unit_missionID) - - local data = Perun.Handlers["event"]("landing") - data["who"] = playerID; - data["unitId"] = unit_missionID; - data["at"] = airdromeName; - - return data; -end ---"pilot_death", playerID, unit_missionID -Perun.Handlers["pilot_death"] = function(playerID, unit_missionID) - Perun.func.ensureUnitId(unit_missionID) - - local data = Perun.Handlers["event"]("pilot_death") - data["who"] = playerID; - data["unitId"] = unit_missionID; - - return data; -end - -net.log("[Perun] Handlers defined") - ---- ################################ Helper function definitions ################################ - -Perun.AddLog = function(text, LogLevel) - -- Adds logs to DCS.log file - if Perun.DebugMode >= LogLevel then - net.log("[Perun]", text) - end -end - -Perun.generate_mission_hash = function() - -- Generates unique simulation mission hash - return DCS.getMissionName( ).."@".. Perun.Instance .. "@" .. Perun.Version .. "@".. os.date('%Y%m%d_%H%M%S'); -end - ---- ################################ TCP Connection ################################ - -Perun.ConnectToPerun = function () - - Perun.AddLog(string.format("Connecting to TCP server %s:%i", Perun.host, Perun.port), 0); - - local status = Perun.DLL.tcpConnect(Perun.host, Perun.port); - - if(status == 0) then - Perun.AddLog(string.format("Connection failed: %s:%i", Perun.host, Perun.port), 0); - else - Perun.State.connected = true; - end -end - ---- ################################ Log functions ################################ - -Perun.LogChat = function(playerID, msg, all) - - local data = Perun.Handlers["event"]("chat"); - - data['player']= Perun.Mission.Players[playerID] - data['msg']=msg - data['messageToAll']=all - data['missionhash']=Perun.MissionHash - - Perun.func.sendData(net.lua2json(data)); -end - -Perun.LogEvent = function(log_type, log_content, log_arg_1, log_arg_2) - -- Logs events messages - local data = Perun.Handlers["event"]("customLog"); - data['log_type']= log_type - data['log_arg_1']= log_arg_1 - data['log_arg_2']= log_arg_2 - data['log_content']=log_content - data['log_missionhash']=Perun.MissionHash - - Perun.AddLog("Sending event data, event: " .. log_type .. ", arg1:" .. log_arg_1 .. ", arg2:" .. log_arg_2 .. ", content: " .. log_content,1) - - Perun.func.sendData(net.lua2json(data)); -end - ---- ################################ Event callbacks ################################ - -Perun.onSimulationStart = function() - -- Simulation was started - Perun.MissionHash=Perun.generate_mission_hash() - Perun.LogEvent("SimStart","Mission " .. Perun.MissionHash .. " started",nil,nil); - Perun.lastSentMission = 0 -- reset so mission information will be send -end - -Perun.onSimulationStop = function() - -- Simulation was stopped - Perun.LogEvent("SimStop","Mission " .. Perun.MissionHash .. " finished",nil,nil); - Perun.MissionHash=Perun.generate_mission_hash(); - - Perun.Mission = {} - Perun.Mission.Slots = {} - Perun.Mission.Slots["blue"] = {} - Perun.Mission.Slots["red"] = {} - Perun.Mission.Players = {} - Perun.Mission.Units = {} -end - -Perun.onPlayerDisconnect = function(id, err_code) - -- Player disconnected - Perun.LogEvent("disconnect", "Player " .. id .. " disconnected.",nil,nil); - - return -end - -Perun.onPlayerStop = function (id) - -- Player left the simulation (happens right before a disconnect, if player exited by desire) - - - Perun.LogEvent("quit", "Player " .. id .. " quit the server.",nil,nil); - return -end - -Perun.onSimulationFrame = function() - - if(not Perun.State.slotsInitialised) then - local foo = Perun.func.ensureSlots; - - local status, error = pcall(foo); - if(status) then - Perun.AddLog( "slot information loaded") - else - Perun.AddLog( "error getting slot information", error) - end - end - - local now = DCS.getRealTime() - --net.log("Perun", "big updates check, now", now, "last sent slots", Perun.Stats.LastSentSlots, "last sent mission", Perun.Stats.LastSentMission) - - if(now - Perun.Stats.LastBigUpdate > Perun.bigUpdatesSeconds) then - if(now > Perun.Stats.LastSentSlots + Perun.refreshSlotsSeconds) then - local payload = Perun.Handlers["event"]("slots") - payload["slots"] = Perun.Mission.Slots; - - local json = net.lua2json(payload) - --net.log("Perun, slots", json) - Perun.func.sendData(json) - Perun.Stats.LastSentSlots = now - Perun.Stats.LastBigUpdate = now - elseif (now > Perun.Stats.LastSentMission + Perun.refreshMissionSeconds) then - local payload = Perun.Handlers["event"]("mission") - payload["mission"] = DCS.getCurrentMission()["mission"] - - local json = net.lua2json(payload); - --net.log("Perun, mission", json) - Perun.func.sendData(json) - Perun.Stats.LastSentMission = now - Perun.Stats.LastBigUpdate = now - end - end - - if(now > Perun.LastSentTCP + Perun.RefreshKeepAlive) then - Perun.func.sendData(" ") - end - - Perun.Stats.PreviousFrameStart = DCS.getRealTime() -end - -Perun.onPlayerStart = function (playerId) - -- Player entered cocpit - - net.send_chat_to(Perun.MOTD_line1, playerId); - net.send_chat_to(Perun.MOTD_line2, playerId); - -end - -Perun.onPlayerTrySendChat = function (playerID, msg, all) - -- Somebody tries to send chat message - if (msg == Perun.ConnectionError or msg == Perun.MOTD_line1 or msg == Perun.MOTD_line2) then - return msg; - end - - Perun.LogChat(playerID,msg,all) - return msg; -end - -Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7) - local argCount = Perun.HandlerArgCounts[eventName] - local handler = Perun.Handlers[eventName] - - local payload = {} - if(argCount == 1) then - payload = handler(arg1) - elseif argCount == 2 then - payload = handler(arg1, arg2) - elseif argCount == 3 then - payload = handler(arg1, arg2, arg3) - elseif argCount == 4 then - payload = handler(arg1, arg2, arg3, arg4) - else - payload = handler(arg1, arg2, arg3, arg4, arg5, arg6, arg7) - end - - local json = net.lua2json(payload) - Perun.func.sendData(json) -end - --- ########### Finalize and set callbacks ########### -if DCS.isServer() then - -- If this game instance is hosting multiplayer game, start Perun - Perun.DLL.StartOfApp(); -- Perun dll StartOfApp callback - Perun.MissionHash=Perun.generate_mission_hash(); -- Generate initial missionhash - DCS.setUserCallbacks(Perun); -- Set user callbacs, map DCS event handlers with functions defined above - Perun.ConnectToPerun(); -- Connect to Perun server - Perun.AddLog("Loaded - Perun for DCS World - version: " .. Perun.Version,0) -- Display perun information in log -end - - diff --git a/01_DCS/Scripts/Hooks/lifecycle.lua b/01_DCS/Scripts/Hooks/lifecycle.lua new file mode 100644 index 0000000..aaf81de --- /dev/null +++ b/01_DCS/Scripts/Hooks/lifecycle.lua @@ -0,0 +1,99 @@ +local explorer = {} + + +explorer.onMissionLoadBegin = function() + net.log("[Explorer]", "on mission load begin"); +end +explorer.onMissionLoadProgress = function(progress, message) + net.log("[Explorer]", "on mission load progress", progress, message); +end +explorer.onMissionLoadEnd = function() + net.log("[Explorer]", "on mission load end"); +end +explorer.onSimulationStart = function() + net.log("[Explorer]", "on simulation start"); +end +explorer.onSimulationStop = function() + net.log("[Explorer]", "on simulation stop"); +end +explorer.onSimulationFrame = function() end +explorer.onSimulationPause= function() + net.log("[Explorer]", "on simulation pause"); +end +explorer.onSimulationResume= function() + net.log("[Explorer]", "on simulation resume"); +end + +explorer.onNetConnect= function() + et.log("[Explorer]", "onNetConnect"); +end +explorer.onNetMissionChanged= function() + net.log("[Explorer]", "onNetMissionChanged"); +end +explorer.onNetConnect= function() + net.log("[Explorer]", "onNetConnect"); +end +explorer.onNetDisconnect= function() + net.log("[Explorer]", "onNetDisconnect"); +end +explorer.onPlayerConnect= function() + net.log("[Explorer]", "onPlayerConnect"); +end +explorer.onPlayerDisconnect= function() + net.log("[Explorer]", "onPlayerDisconnect"); +end +explorer.onPlayerStart= function() + net.log("[Explorer]", "onPlayerStart"); +end +explorer.onPlayerStop= function() + net.log("[Explorer]", "onPlayerStop"); +end +explorer.onPlayerChangeSlot= function() + net.log("[Explorer]", "onPlayerChangeSlot"); +end +explorer.onPlayerTryConnect= function(address, name, ucid, id) + net.log("[Explorer]", "onPlayerTryConnect", address, name, ucid, id); +end +explorer.onPlayerTrySendChat= function() + net.log("[Explorer]", "onPlayerTrySendChat"); +end +explorer.onPlayerTryChangeSlot= function() + net.log("[Explorer]", "onPlayerTryChangeSlot"); +end + +explorer.onGameEvent = function(eventName, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + + local payload = {} + if(argCount == 1) then + payload = handler(arg1); + elseif argCount == 2 then + payload = handler(arg1, arg2); + elseif argCount == 3 then + payload = handler(arg1, arg2, arg3); + elseif argCount == 4 then + payload = handler(arg1, arg2, arg3, arg4); + elseif argCount == 7 then + payload = handler(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + end + + local json = net.lua2json(payload); + net.log("[Explorer]", json); + + --net.log("[Pierog] json", json) + + --net.log("[Pierog]", "get_player_list", net.lua2json(net.get_player_list())) + --net.log("[Pierog]", "mission_data", net.lua2json(DCS.getCurrentMission())) + + --net.log("[Pierog]", "DCS.getAvailableCoalitions()", net.lua2json(DCS.getAvailableCoalitions())) + --net.log("[Pierog]", "DCS.getAvailableSlots(0)", DCS.getAvailableSlots(0)) + --net.log("[Pierog]", "DCS.getAvailableSlots(3)", DCS.getAvailableSlots(3)) + --net.log("[Pierog]", "DCS.getMissionName()", DCS.getMissionName()) + --net.log("[Pierog]", "DCS.getModelTime()", DCS.getModelTime()) + --net.log("[Pierog]", "DCS.getRealTime()", DCS.getRealTime()) +end + + +if DCS.isServer() then + net.log("[Explorer]", "Initialising API explorer"); + DCS.setUserCallbacks(explorer); +end \ No newline at end of file diff --git a/01_DCS/Scripts/Hooks/pierog_light_hook.lua b/01_DCS/Scripts/Hooks/pierog_light_hook.lua new file mode 100644 index 0000000..3535594 --- /dev/null +++ b/01_DCS/Scripts/Hooks/pierog_light_hook.lua @@ -0,0 +1,530 @@ +-- Pierog for DCS World, inspired by +-- https://github.com/szporwolik/perun -> DCS Hook component +net.log("[Pierog] Loading Pierog") + +-- Initial init +local Pierog = {} + +-- Load Luas +package.path = package.path..";"..lfs.currentdir().."/LuaSocket/?.lua"..";"..lfs.writedir() .. "/Mods/services/Pierog/lua/?.lua" +package.cpath = package.cpath..";"..lfs.currentdir().."/LuaSocket/?.dll" + +-- Load Dlls +package.cpath = package.cpath..';'.. lfs.writedir()..'/Mods/services/Pierog/bin/' ..'?.dll;' +net.log("[Pierog]", "Loading DLL") +Pierog.DLL = require('pierog') + +-- Load config file +net.log("[Pierog]", "Loading config") +local config = require "pierog_config" + +Pierog.host = config.host; +Pierog.port = config.port; +Pierog.delimiters = config.delimiters; +Pierog.Instance = config.Instance; +Pierog.DebugMode = config.DebugMode; +Pierog.logPath = config.logPath; + +Pierog.MOTD_line1 = config.MOTD_line1 +Pierog.MOTD_line2 = config.MOTD_line2 +Pierog.ConnectionError = config.ConnectionError +Pierog.BroadcastPierogErrors = config.BroadcastPierogErrors +Pierog.BroadcastErrorEachSeconds = config.BroadcastErrorEachSeconds or 600 + +Pierog.keepAliveSeconds = config.keepAliveSeconds +Pierog.bigUpdatesSeconds = config.bigUpdatesSeconds +Pierog.refreshSlotsSeconds = config.refreshSlotsSeconds +Pierog.refreshMissionSeconds = config.refreshMissionSeconds + +net.log("[Pierog]", "Config loaded") + +-- to avoid declaring every time a function is run +Pierog.Globals = {} +Pierog.Globals.version = "v0.12.1" +Pierog.Globals.Sides = { + [0] = 'SPECTATOR', + [1] = 'RED', + [2] = 'BLUE', + [3] = 'NEUTRAL', -- TBD check once this is released in DCS +} + +net.log("[Pierog]", "Global variables initialised") + +--net.log("[Pierog]", "Dupa") + +Pierog.lastSentStatus = 0 +Pierog.lastSentMission = 0 +Pierog.lastSentKeepAlive = 0 +Pierog.lastConnectionError = 0 +Pierog.lastFrameStart = 0; +Pierog.lastTimer = 0 +Pierog.lastFrameTime = 0; + +Pierog.ReconnectTimeout = 30; +Pierog.RefreshKeepAlive = 5 + +--net.log("[Pierog]", "Dupa2") + +Pierog.State = {} +Pierog.State.connected = false; +Pierog.State.slotsInitialised = false; + +--net.log("[Pierog]", "Dupa3") + +Pierog.Mission = {} +Pierog.Mission.Hash = {} +Pierog.Mission.Slots = {} +Pierog.Mission.Players = {} +Pierog.Mission.Units = {} + +net.log("[Pierog]", "Initialising handlers") + +Pierog.HandlerArgCounts = {} + +Pierog.HandlerArgCounts["self_kill"] = 1 +Pierog.HandlerArgCounts["crash"] = 2 +Pierog.HandlerArgCounts["eject"] = 2 +Pierog.HandlerArgCounts["connect"] = 2 +Pierog.HandlerArgCounts["mission_end"] = 2 +Pierog.HandlerArgCounts["pilot_death"] = 2 +Pierog.HandlerArgCounts["takeoff"] = 3 +Pierog.HandlerArgCounts["landing"] = 3 +Pierog.HandlerArgCounts["change_slot"] = 3 +Pierog.HandlerArgCounts["friendly_fire"] = 3 +Pierog.HandlerArgCounts["disconnect"] = 4 +Pierog.HandlerArgCounts["kill"] = 7 + +Pierog.Stats = {} +Pierog.Stats.LastSentTCP = 0 +Pierog.Stats.LastSentSlots = 0 +Pierog.Stats.LastBigUpdate = 0 +Pierog.Stats.LastSentMission = 0 +Pierog.Stats.LastConnectionError = 0 + +Pierog.func = {}; +Pierog.func.ensureSlots = function() + if(not Pierog.State.slotsInitialised) then + + local count = 0; + local coalitions = DCS.getAvailableCoalitions(); + for name, _ in pairs(coalitions) do + local slots = DCS.getAvailableSlots(name); + Pierog.Mission.Slots[name] = {} + for k, unit in pairs(slots) do + Pierog.Mission.Slots[name][unit["unitId"]] = unit + count = count + 1; + end + end + + if(count > 0) then + net.log("[Pierog]", "initialised ".. count .. " slots") + Pierog.State.slotsInitialised = true; + end + end +end + +Pierog.func.ensureUnitId = function(unitId) + if(not Pierog.Mission.Units[unitId]) then + local unitType = DCS.getUnitType(unitId); + Pierog.Mission.Units[unitId] = unitType; + + net.log("[Pierog]", unitId, "category:", DCS.getUnitTypeAttribute(unitType, "category")); + net.log("[Pierog]", unitId, "category via type -> Prop:", DCS.getUnitProperty(unitType, DCS.UNIT_CATEGORY)); + net.log("[Pierog]", unitId, "category via id -> Prop:", DCS.getUnitProperty(unitId, DCS.UNIT_CATEGORY)); + net.log("[Pierog]", unitId, "wing span:", DCS.getUnitTypeAttribute(unitType, "WingSpan")); + net.log("[Pierog]", unitId, "deck level", DCS.getUnitTypeAttribute(unitType, "DeckLevel")); + -- + local payload = Pierog.Handlers["event"]("unit_definition"); + payload["unitId"] = unitId; + payload["unitType"] = unitType; + payload["unitCategory"] = DCS.getUnitTypeAttribute(unitId, "category"); + payload["wingSpan"] = DCS.getUnitTypeAttribute(unitType, "WingSpan"); + payload["deckLevel"] = DCS.getUnitTypeAttribute(unitType, "DeckLevel"); + -- + Pierog.func.sendData(net.lua2json(payload)) + end +end + +Pierog.func.sendData = function(payload) + + local now = DCS.getRealTime() + + local connected, reconnected = Pierog.DLL.tcpSend(payload) + --net.log("[Pierog]", connected, reconnected) + if(connected < 1) and (now > Pierog.Stats.LastConnectionError + Pierog.ReconnectTimeout) then + Pierog.AddLog("ERROR - TCP connection not available",0) + + if Pierog.BroadcastPierogErrors > 0 then + local _all_players = net.get_player_list() + for i, playerId in ipairs(_all_players) do + net.send_chat_to(Pierog.ConnectionError , playerId) + end + end + + Pierog.Stats.LastConnectionError = now + else + Pierog.Stats.LastSentTCP = now + + if(reconnected > 0) then + net.log("[Pierog]", "TCP reconnected") + Pierog.Stats.LastBigUpdate = 0 + Pierog.Stats.LastSentSlots = 0 + Pierog.Stats.LastSentMission = 0 + end + end +end + +Pierog.func.sendMissionUpdate = function() + local payload = Pierog.Handlers["event"]("mission") + payload["mission"] = DCS.getCurrentMission()["mission"] + payload["missionName"] = DCS.getCurrentMission() + + local json = net.lua2json(payload); + Pierog.func.sendData(json) +end + +Pierog.func.sendSlotUpdate = function() + local payload = Pierog.Handlers["event"]("slots") + payload["slots"] = Pierog.Mission.Slots; + + local json = net.lua2json(payload) + Pierog.func.sendData(json) +end + +Pierog.Handlers = {} +Pierog.Handlers["event"] = function(eventName) + local data = {}; + data["event"] = eventName; + data["missionHash"] = Pierog.Mission.Hash + data['datetime']=os.date('%Y-%m-%d %H:%M:%S') + + return data; +end + +Pierog.Handlers["friendly_fire"] = function(playerID, weaponName, victimPlayerID) + local data = Pierog.Handlers["event"]("friendly_fire") + + data["killer"] = playerID; + data["victim"] = victimPlayerID; + data["weapon"] = weaponName; + + return data; +end + +Pierog.Handlers["mission_end"] = function(winner, msg) + local data = Pierog.Handlers["event"]("mission_end") + + data["winner"] = winner; + data["message"] = msg; + + return data; +end + +Pierog.Handlers["kill"] = function(killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName) + local data = Pierog.Handlers["event"]("kill") + + data["killer"] = {id = killerPlayerID, side = killerSide, type = killerUnitType, ucid = net.get_player_info(killerPlayerID)["ucid"]} + data["victim"] = {id = victimPlayerID, side = victimSide, type = victimUnitType, ucid = net.get_player_info(victimPlayerID)["ucid"]} + data["weapon"] = weaponName; + + return data; +end + +Pierog.Handlers["self_kill"] = function(playerID) + local data = Pierog.Handlers["event"]("self_kill") + data["who"] = playerID; + + return data; +end + +Pierog.Handlers["change_slot"] = function(playerID, slotID, prevSide) + local newDetails = net.get_player_info(playerID); + local oldDetails = Pierog.Mission.Players[playerID]; + + Pierog.Mission.Players[playerID] = newDetails; + + local data = Pierog.Handlers["event"]("change_slot") + data["previousSlot"] = oldDetails["slot"] + data["previousSide"] = oldDetails["side"] + data["side"] = newDetails["side"] + data["slot"] = newDetails["slot"] + data["ucid"] = newDetails["ucid"] + + return data; +end + +Pierog.Handlers["connect"] = function(playerID, name) + net.log("[Pierog]", "Getting player details") + local details = net.get_player_info(playerID) + Pierog.Mission.Players[playerID] = details + + local data = Pierog.Handlers["event"]("connect") + data["who"] = playerID + data["details"] = details; + + return data; +end + +Pierog.Handlers["disconnect"] = function(playerID, name, playerSide, reason_code) + local data = Pierog.Handlers["event"]("disconnect") + data["who"] = Pierog.Mission.Players[playerID] + data["reasonCode"] = reason_code; + + Pierog.Mission.Players[playerID] = nil + + return data; +end +--"crash", playerID, unit_missionID +Pierog.Handlers["crash"] = function(playerID, unit_missionID) + Pierog.func.ensureUnitId(unit_missionID) + + local data = Pierog.Handlers["event"]("crash") + data["who"] = playerID; + data["unitId"] = unit_missionID; + + return data; +end +--"eject", playerID, unit_missionID +Pierog.Handlers["eject"] = function(playerID, unit_missionID) + Pierog.func.ensureUnitId(unit_missionID) + + local data = Pierog.Handlers["event"]("eject") + data["who"] = playerID; + data["unitId"] = unit_missionID; + + return data; +end +--"takeoff", playerID, unit_missionID, airdromeName +Pierog.Handlers["takeoff"] = function(playerID, unit_missionID, airdromeName) + Pierog.func.ensureUnitId(unit_missionID) + + local data = Pierog.Handlers["event"]("takeoff") + data["who"] = playerID; + data["unitId"] = unit_missionID; + data["from"] = airdromeName; + + return data; +end +--"landing", playerID, unit_missionID, airdromeName +Pierog.Handlers["landing"] = function(playerID, unit_missionID, airdromeName) + Pierog.func.ensureUnitId(unit_missionID) + + local data = Pierog.Handlers["event"]("landing") + data["who"] = playerID; + data["unitId"] = unit_missionID; + data["at"] = airdromeName; + + return data; +end +--"pilot_death", playerID, unit_missionID +Pierog.Handlers["pilot_death"] = function(playerID, unit_missionID) + Pierog.func.ensureUnitId(unit_missionID) + + local data = Pierog.Handlers["event"]("pilot_death") + data["who"] = playerID; + data["unitId"] = unit_missionID; + + return data; +end + +net.log("[Pierog] Handlers defined") + +--- ################################ Helper function definitions ################################ + +Pierog.AddLog = function(text, logLevel) + -- Adds logs to DCS.log file + if(logLevel == nil) then + logLevel = -1 + end + + if Pierog.DebugMode >= logLevel then + net.log("[Pierog]", text) + end +end + +Pierog.generate_mission_hash = function() + -- Generates unique simulation mission hash + return DCS.getMissionName( ).."@".. Pierog.Instance .. "@" .. Pierog.Globals.version .. "@".. os.date('%Y%m%d_%H%M%S'); +end + +--- ################################ Log functions ################################ + +Pierog.LogChat = function(playerID, msg, all) + + local data = Pierog.Handlers["event"]("chat"); + + data['who']= Pierog.Mission.Players[playerID]["ucid"] + data['msg']=msg + data['messageToAll']=all + data['missionhash']=Pierog.Mission.Hash + + Pierog.func.sendData(net.lua2json(data)); +end + +Pierog.LogEvent = function(log_type, log_content, log_arg_1, log_arg_2) + -- Logs events messages + local data = Pierog.Handlers["event"]("customLog"); + data['log_type']= log_type + data['log_arg_1']= log_arg_1 + data['log_arg_2']= log_arg_2 + data['log_content']=log_content + data['log_missionhash']=Pierog.Mission.Hash + + Pierog.AddLog("Sending event data, event: " .. log_type .. ", arg1:" .. log_arg_1 .. ", arg2:" .. log_arg_2 .. ", content: " .. log_content,1) + + Pierog.func.sendData(net.lua2json(data)); +end + +--- ################################ Event callbacks ################################ + +Pierog.onSimulationStart = function() + Pierog.Mission.Hash=Pierog.generate_mission_hash() + Pierog.LogEvent("SimStart","Mission " .. Pierog.Mission.Hash .. " started",nil,nil); + Pierog.lastSentMission = 0 -- reset so mission information will be sent + Pierog.State.slotsInitialised = false; + + Pierog.DLL.markMissionStart(Pierog.Mission.Hash); +end + +Pierog.onSimulationStop = function() + Pierog.LogEvent("SimStop","Mission " .. Pierog.Mission.Hash .. " finished",nil,nil); + + Pierog.Mission = {} + Pierog.Mission.Hash=Pierog.generate_mission_hash(); + Pierog.Mission.Slots = {} + Pierog.Mission.Players = {} + Pierog.Mission.Units = {} + + Pierog.Stats.LastSentSlots = 0 + Pierog.Stats.LastBigUpdate = 0 + Pierog.Stats.LastSentMission = 0 + Pierog.DLL.markMissionStart(Pierog.Mission.Hash); +end + +Pierog.onPlayerStop = function (id) + -- Player left the simulation (happens right before a disconnect, if player exited by desire) + + Pierog.LogEvent("quit", "Player " .. id .. " quit the server.",nil,nil); + return +end + +Pierog.onSimulationFrame = function() + + if(DCS.getMissionName() == "") then + -- no mission loaded + return; + end + + local now = DCS.getRealTime() + + if(Pierog.Stats.LastBigUpdate < 1 or now > Pierog.Stats.LastBigUpdate + Pierog.bigUpdatesSeconds) then + --net.log("[Pierog]", "Big update evaluation at", now, ", Pierog.Stats.LastSentSlots", Pierog.Stats.LastSentSlots, ", Pierog.Stats.LastBigUpdate", Pierog.Stats.LastBigUpdate) + --net.log("[Pierog]", "Pierog.refreshSlotsSeconds: ".. Pierog.refreshSlotsSeconds) + --net.log("[Pierog]", "name: >"..DCS.getMissionName().."<"); + --net.log("[Pierog]", "mission"..net.lua2json(DCS.getCurrentMission())) + + if(not Pierog.State.slotsInitialised) then + local foo = Pierog.func.ensureSlots; + + local status, error = pcall(foo); + if(error) then + Pierog.AddLog( "error getting slot information" .. error); + else + Pierog.func.sendSlotUpdate(); + Pierog.Stats.LastSentSlots = now; + Pierog.Stats.LastBigUpdate = now; + end + elseif(now > Pierog.Stats.LastSentSlots + Pierog.refreshSlotsSeconds) then + --net.log("[Pierog]", "Slots") + + Pierog.func.sendSlotUpdate(); + Pierog.Stats.LastSentSlots = now; + Pierog.Stats.LastBigUpdate = now; + end + + if (now > Pierog.Stats.LastSentMission) then + --net.log("[Pierog]", "Mission") + + Pierog.func.sendMissionUpdate() + Pierog.Stats.LastSentMission = now + Pierog.Stats.LastBigUpdate = now + end + end + + if(now > Pierog.LastSentTCP + Pierog.RefreshKeepAlive) then + Pierog.func.sendData(" ") + end +end + +Pierog.onPlayerStart = function (playerId) + -- Player entered cocpit + net.send_chat_to(Pierog.MOTD_line1, playerId); + net.send_chat_to(Pierog.MOTD_line2, playerId); +end + +Pierog.onPlayerTrySendChat = function (playerID, msg, all) + -- Somebody tries to send chat message + if (msg == Pierog.ConnectionError or msg == Pierog.MOTD_line1 or msg == Pierog.MOTD_line2) then + return msg; + end + + Pierog.LogChat(playerID, msg, all) + return msg; +end + +Pierog.onGameEvent = function(eventName, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + local argCount = Pierog.HandlerArgCounts[eventName] + local handler = Pierog.Handlers[eventName] + + if(Pierog.Handlers[eventName] == nil) then + -- no handler found + net.log("[Pierog]", 'Event type not found'..eventName) + return; + end + + local payload = {} + if(argCount == 1) then + payload = handler(arg1) + elseif argCount == 2 then + payload = handler(arg1, arg2) + elseif argCount == 3 then + payload = handler(arg1, arg2, arg3) + elseif argCount == 4 then + payload = handler(arg1, arg2, arg3, arg4) + elseif argCount == 7 then + payload = handler(arg1, arg2, arg3, arg4, arg5, arg6, arg7) + end + + local json = net.lua2json(payload) + Pierog.func.sendData(json) + + if(eventName == "mission_end") then + Pierog.DLL.markMissionStart(Pierog.Mission.Hash); + end + + --net.log("[Pierog] json", json) + + --net.log("[Pierog]", "get_player_list", net.lua2json(net.get_player_list())) + --net.log("[Pierog]", "mission_data", net.lua2json(DCS.getCurrentMission())) + + --net.log("[Pierog]", "DCS.getAvailableCoalitions()", net.lua2json(DCS.getAvailableCoalitions())) + --net.log("[Pierog]", "DCS.getAvailableSlots(0)", DCS.getAvailableSlots(0)) + --net.log("[Pierog]", "DCS.getAvailableSlots(3)", DCS.getAvailableSlots(3)) + --net.log("[Pierog]", "DCS.getMissionName()", DCS.getMissionName()) + --net.log("[Pierog]", "DCS.getModelTime()", DCS.getModelTime()) + --net.log("[Pierog]", "DCS.getRealTime()", DCS.getRealTime()) +end + +-- ########### Finalize and set callbacks ########### +if DCS.isServer() then + -- If this game instance is hosting multiplayer game, start Pierog + net.log("[Pierog]", "In a server, setting DLL", Pierog.logPath, Pierog.host, Pierog.port) + Pierog.DLL.delimiters(Pierog.delimiters[1], Pierog.delimiters[2]) + Pierog.DLL.StartOfApp(Pierog.logPath, Pierog.host, Pierog.port); + net.log("[Pierog]", "DLL started") + Pierog.Mission.Hash=Pierog.generate_mission_hash(); -- Generate initial missionhash + DCS.setUserCallbacks(Pierog); -- Set user callbacs, map DCS event handlers with functions defined above + Pierog.AddLog("Loaded - Pierog for DCS World - version: " .. Pierog.Globals.version,0) -- Display Pierog information in log +end + + diff --git a/01_DCS/Scripts/notes.md b/01_DCS/Scripts/notes.md new file mode 100644 index 0000000..ace12e7 --- /dev/null +++ b/01_DCS/Scripts/notes.md @@ -0,0 +1,55 @@ + + +|call |return | meaning | +|-------- |---- |---- | +|`DCS.getAvailableSlots("blue")`|TBD|slots available for a side| +|`net.get_player_list()`|[1, 2]|players in the mission. As in, occupied player slots in the server| +||| +||| +|`DCS.getRealTime()` | `37.5899104` | seconds.micros after mission was started | +|`DCS.getModelTime()`|`37.413`| + + + +Callbacks: + +|callback|event|| +|----|----|----| +|onMissionLoadBegin||| +|onMissionLoadProgress||| +|onMissionLoadEnd, ||| +|onSimulationStart, ||| +|onSimulationStop, ||| +|onSimulationFrame, ||| +|onSimulationPause, ||| +|onSimulationResume, ||| +|onGameEvent, ||| +|onNetConnect, ||| +|onNetMissionChanged,||| +|onNetConnect, ||| +|onNetDisconnect, ||| +|onPlayerTryConnect, ||| +|onPlayerConnect, |_connect|| +|onPlayerDisconnect, |disconnect|| +|onPlayerStart, ||| +|onPlayerStop, ||| +|onPlayerTrySendChat, ||| +|onPlayerTryChangeSlot||| +|onPlayerChangeSlot,|change_slot|| + + +# Q's: +Is unit ID tracking needed? + +# ToDos: +* file per mission hash +* start of mission cpp callback? +* hash as argument +* end as callback +* investigate DCS namespace (for pairs?) + +* file creation and writing to file should only happen when there is something to be written + +* mission update should happen every X seconds, only +* connection should only affect message to players, nothing else! +* detect monotonic time flow - back in time? new mission. \ No newline at end of file diff --git a/01_DCS/dcs_engine_events.puml b/01_DCS/dcs_engine_events.puml new file mode 100644 index 0000000..225f840 --- /dev/null +++ b/01_DCS/dcs_engine_events.puml @@ -0,0 +1,38 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +participant UI +participant Server +participant Mission +participant Simulation +participant Paused +participant Active + +UI -> Server: onNetMissionChange +note left: start server +Server -> Mission: onMissionLoadBegin +Server -> Mission: onMissionLoadEnd + +Mission -> Paused: onSimulationStart +note right: if starting configured, will unpause automatically +Paused -> Active: onSimulationResume + + +Active -> Paused: onSimulationPaused +note left: when paused in the UI +Paused -> Active: onSimulationResume +note right: when simulation is unpaused + +Simulation -> Mission: onSimulationStop +note right: when simulation is stopped + + +UI -> Server: onNetMissionChange +note left: change mission +Server -> Mission: onMissionLoadBegin + +@enduml + + diff --git a/03_Perun_Lua_Wrapper/pierog/src/DataDistributor.cpp b/03_Perun_Lua_Wrapper/pierog/src/DataDistributor.cpp index 0871688..863430e 100644 --- a/03_Perun_Lua_Wrapper/pierog/src/DataDistributor.cpp +++ b/03_Perun_Lua_Wrapper/pierog/src/DataDistributor.cpp @@ -1,6 +1,7 @@ #include "DataDistributor.h" #include +#include DataDistributor::DataDistributor(std::string logPath, std::string host, @@ -18,22 +19,24 @@ void DataDistributor::start() { } std::thread thread_object([this]() { - bool anythingToSend = true; long KEEP_ALIVE = 1000; - auto now = std::chrono::system_clock::now(); + bool nothingToSend = false; while(shouldRun) { + auto now = std::chrono::system_clock::now(); + if(lock.try_lock()) { if(sendQueue.empty()) { auto delay = std::chrono::duration_cast(now - lastSent); if(delay.count() > KEEP_ALIVE) { sendQueue.push_front(new std::string(" ")); } else { - anythingToSend = false; + nothingToSend = true; } } else { auto payload = sendQueue.front(); int sent = socketOutput->write(payload); + lastSent = std::chrono::system_clock::now(); if(sent > 0) { everSentViaSocket = true; @@ -45,7 +48,6 @@ void DataDistributor::start() { sendQueue.push_front(&shortened); delete payload; } - lastSent = std::chrono::system_clock::now(); } else { // failed to send } @@ -54,9 +56,7 @@ void DataDistributor::start() { lock.unlock(); } - if(anythingToSend) { - Sleep(10); - } else { + if(nothingToSend) { Sleep(100); } } diff --git a/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.cpp b/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.cpp index 2a2204b..05a5860 100644 --- a/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.cpp +++ b/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.cpp @@ -18,10 +18,6 @@ int SocketOutput::write(std::string *payload) { } int bytesSent = send(tcpSocket, payload->c_str(), payload->length(), 0); - if(bytesSent == payload->length()) { - return 0; - } - if(bytesSent > 0) { return bytesSent; } else { diff --git a/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.h b/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.h new file mode 100644 index 0000000..1086984 --- /dev/null +++ b/03_Perun_Lua_Wrapper/pierog/src/SocketOutput.h @@ -0,0 +1,38 @@ +#ifndef PARENT_SOCKETOUTPUT_H +#define PARENT_SOCKETOUTPUT_H + +#include "winsock.h" +#include +#include + +enum enumConnectionState { + DISCONNECTED, + CONNECTED, + NEVER_CONNECTED +}; + +class SocketOutput { +public: + SocketOutput(std::string host, + int port); + virtual ~SocketOutput(); + + int write(std::string* payload); + + bool isConnected(); +private: + SOCKET tcpSocket = INVALID_SOCKET; + SOCKADDR_IN* address = nullptr; + + const std::string tcpHost = "localhost"; + const int tcpPort = 0; + + volatile enumConnectionState connectionState = NEVER_CONNECTED; + + bool _connect(); +// void reconnect(); + void disconnect(); +}; + + +#endif //PARENT_SOCKETOUTPUT_H