9 Commits

Author SHA1 Message Date
ags
d02b6d4e78 ignore cmake release 2021-12-20 17:03:34 +00:00
ags
1bc0f142bd rewritten hook 2021-12-20 17:02:15 +00:00
ags
545298f26d renamed hook to pierog 2021-12-13 20:22:00 +00:00
ags
0a0da5b5a9 renamed to pierog, extracted socket and file writers 2021-12-12 23:30:32 +00:00
ags
957dc393bd log mission logs into separate files 2021-12-11 00:45:18 +00:00
ags
b87276cfe3 lighter perun event listener 2021-11-27 20:56:52 +00:00
Szymon Porwolik
f46ffd9385 Merge pull request #65 from szporwolik/dev
Dev
2021-03-01 16:13:01 +01:00
Szymon Porwolik
ee19bd46f3 Merge pull request #64 from szporwolik/dev
Updated MySQL
2021-02-28 12:42:58 +01:00
Szymon Porwolik
eb474de312 Merge pull request #63 from szporwolik/dev
Update readme.md
2021-02-28 12:33:17 +01:00
27 changed files with 1254 additions and 321 deletions

4
.gitignore vendored
View File

@@ -261,4 +261,6 @@ __pycache__/
*.pyc
# cmake folders
cmake-build-debug
cmake-build-debug
cmake-build-release

View File

@@ -1,26 +0,0 @@
-- Perun for DCS World https://github.com/szporwolik/perun -> DCS Hook config component
local PerunConfig = {}
-- ###################### SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION #############################
-- Connection
PerunConfig.TCPPerunHost = "localhost" -- (string) [default: "localhost"] IP adress of the Perun instance or "localhost"
PerunConfig.TCPTargetPort = 48621 -- (int) [default: 48621] TCP port to send data to
PerunConfig.Instance = 1 -- (int) [default: 1] Id number of instance (if multiple DCS instances are to run at the same PC)
PerunConfig.RefreshStatus = 60 -- (int) [default: 60] Base refresh rate in seconds to send status update
-- Mission Configuration
PerunConfig.MissionStartNoDeathWindow = 300 -- (int) [default: 300] Number of secounds after mission start when death of the pilot will not go to statistics, shall avoid death penalty during spawning DCS bugs
-- Localisation
PerunConfig.MOTD_L1 = "[Perun] Welcome to our server !" -- (string) Message send to players connecting the server - Line 1
PerunConfig.MOTD_L2 = "[Perun] Stats and event data integrated with Perun for DCS World" -- (string) Message send to players connecting the server - Line 2
PerunConfig.ConnectionError_L1 = "[Perun] ERROR: Connection broken - contact server admin!" -- (string) Information to send to players when Perun connection is broken
-- Misc
PerunConfig.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun
-- Debug
PerunConfig.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
-- ###################### END OF SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION ######################
return PerunConfig

View File

@@ -0,0 +1,33 @@
-- Perun for DCS World https://github.com/szporwolik/perun -> DCS Hook config component
local config = {}
-- ###################### SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION #############################
-- 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 = {"<SOT>", "<EOT>"}
config.Instance = 1 -- (int) [default: 1] Id number of instance (if multiple DCS instances run on the same machine)
config.keepAliveSeconds = 5 --
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 = 1200 -- delay between slot updates are sent
config.refreshMissionSeconds = 1200 -- delay between mission update is sent
-- Localisation
-- Server MOTD, sent to ALL players when entering cockpit
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 = "[Pierog] ERROR: Connection broken - contact server admin!"
-- Misc
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 = 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

View File

@@ -13,17 +13,18 @@ package.cpath = package.cpath..';'.. lfs.writedir()..'/Mods/services/Perun/bin/'
Perun.DLL = require('perun')
-- Load config file
local PerunConfig = require "perun_config"
Perun.RefreshStatus = PerunConfig.RefreshStatus
Perun.TCPTargetPort = PerunConfig.TCPTargetPort
Perun.TCPPerunHost = PerunConfig.TCPPerunHost
Perun.Instance = PerunConfig.Instance
Perun.MissionStartNoDeathWindow = PerunConfig.MissionStartNoDeathWindow
Perun.DebugMode = PerunConfig.DebugMode
Perun.MOTD_L1 = PerunConfig.MOTD_L1
Perun.MOTD_L2 = PerunConfig.MOTD_L2
Perun.ConnectionError = PerunConfig.ConnectionError_L1
Perun.BroadcastPerunErrors = PerunConfig.BroadcastPerunErrors
local config = require "perun_config"
Perun.RefreshStatus = config.RefreshStatus
Perun.TCPTargetPort = config.port
Perun.TCPPerunHost = config.host
Perun.Instance = config.Instance
Perun.MissionStartNoDeathWindow = config.MissionStartNoDeathWindow
Perun.DebugMode = config.DebugMode
Perun.MOTD_L1 = config.MOTD_L1
Perun.MOTD_L2 = config.MOTD_L2
Perun.ConnectionError = config.ConnectionError_L1
Perun.BroadcastPerunErrors = config.BroadcastPerunErrors
-- Variable init
Perun.Version = "v0.12.1"
@@ -47,6 +48,23 @@ Perun.lastFrameTime = 0;
Perun.ReconnectTimeout = 30;
Perun.RefreshKeepAlive = 3
Perun.State = {}
Perun.State.connected = 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.Coalitions = {}
-- ################################ Helper function definitions ################################
Perun.GetCategory = function(id)
-- Helper function returns object category basing on https://pastebin.com/GUAXrd2U
@@ -75,14 +93,8 @@ end
Perun.SideID2Name = function(id)
-- Helper function returns side name per side (coalition) id
local _sides = {
[0] = 'SPECTATOR',
[1] = 'RED',
[2] = 'BLUE',
[3] = 'NEUTRAL', -- TBD check once this is released in DCS
}
if id > 0 and id <= 3 then
return _sides[id]
return Perun.Globals.Sides[id]
else
return "?"
end
@@ -91,11 +103,11 @@ end
Perun.AddLog = function(text,LogLevel)
-- Adds logs to DCS.log file
if Perun.DebugMode >= LogLevel then
net.log("[Perun] ".. text)
net.log("[Perun] ", text)
end
end
Perun.GenerateMissionHash = function()
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
@@ -239,6 +251,7 @@ Perun.ConnectToPerun = function ()
Perun.DLL.tcpConnect(Perun.TCPPerunHost, Perun.TCPTargetPort)
Perun.AddLog(string.format("Connecting to TCP server %s:%i", Perun.TCPPerunHost, Perun.TCPTargetPort), 2)
Perun.State.connected = true;
end
Perun.SendToPerun = function(data_id, data_package)
@@ -253,7 +266,7 @@ Perun.SendToPerun = function(data_id, data_package)
_payload = net.lua2json(data_package);
end
local _TempData={"<SOT>","{\"dcs_current_frame_delay\":",((DCS.getRealTime() - Perun.lastFrameStart) * 1000000),",\"type\":",data_id,",\"dcs_frame_time\":",(Perun.lastFrameTime * 1000000),",\"instance\":",(Perun.Instance),",\"timestamp\":\"",(os.date('%Y-%m-%d %H:%M:%S')),"\",\"payload\":",(_payload),"}<EOT>"}
local _TempData={"<SOT>","{\"dcs_current_frame_delay\":",((DCS.getRealTime() - Perun.lastFrameStart) * 1000),",\"type\":",data_id,",\"dcs_frame_time\":",(Perun.lastFrameTime * 1000),",\"instance\":",(Perun.Instance),",\"timestamp\":\"",(os.date('%Y-%m-%d %H:%M:%S')),"\",\"payload\":",(_payload),"}<EOT>"}
local _tcpMessage= table.concat( _TempData )
-- TCP Part - sending
@@ -620,7 +633,7 @@ end
Perun.onSimulationStart = function()
-- Simulation was started
Perun.MissionHash=Perun.GenerateMissionHash()
Perun.MissionHash=Perun.generate_mission_hash()
Perun.LogEvent("SimStart","Mission " .. Perun.MissionHash .. " started",nil,nil);
Perun.StatData = {}
Perun.StatDataLastType = {}
@@ -634,7 +647,7 @@ Perun.onSimulationStop = function()
-- Simulation was stopped
Perun.LogEvent("SimStop","Mission " .. Perun.MissionHash .. " finished",nil,nil);
Perun.LogAllStats()
Perun.MissionHash=Perun.GenerateMissionHash();
Perun.MissionHash=Perun.generate_mission_hash();
Perun.StatData = {}
Perun.MissionData = {}
Perun.StatDataLastType = {}
@@ -716,6 +729,24 @@ end
Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
-- Game event has occured
local _now = DCS.getRealTime()
local _payload = net.lua2json({eventName, arg1, arg2, arg3, arg4, arg5, arg6, arg7})
Perun.DLL.tcpSend("<SOT>" .. _payload .. "<EOT>")
for i, value in pairs({ "red", "blue" }) do
-- table { type, unit_missionID }
local slots = DCS.getAvailableSlots(value)
for slot in slots do
local idx = slot[1];
Perun.Mission.Coalitions[value][idx] = {}
Perun.Mission.Coalitions[value][idx]["type"] = slot[2];
Perun.DLL.tcpSend("<SOT>" .. net.lua2json(DCS.getUnitType(idx)) .. "<EOT>")
end
end
local _payload = net.lua2json(Perun.Mission.Coalitions)
Perun.DLL.tcpSend("<SOT> SLOTS " .. _payload .. "<EOT>")
Perun.AddLog("Event handler for ".. eventName .. " started",2)
if eventName == "friendly_fire" then
@@ -873,7 +904,7 @@ end
if DCS.isServer() then
-- If this game instance is hosting multiplayer game, start Perun
Perun.DLL.StartOfApp() -- Start the main Perun dll
Perun.MissionHash=Perun.GenerateMissionHash() -- Generate initial missionhash
Perun.MissionHash=Perun.generate_mission_hash() -- Generate initial missionhash
DCS.setUserCallbacks(Perun) -- Set user callbacs, map DCS event handlers with functions defined above
Perun.AddLog("Loaded - Perun for DCS World - version: " .. Perun.Version,0) -- Display perun information in log
Perun.ConnectToPerun() -- Connect to Perun server

View File

@@ -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

View File

@@ -0,0 +1,497 @@
-- Pierog for DCS World (evolution of Perun, name needs deconfliction for parallel runs)
-- 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")
Pierog.Globals = {}
Pierog.Globals.version = "v0.21.12"
net.log("[Pierog]", "Global variables initialised")
Pierog.ReconnectTimeout = 30;
Pierog.RefreshKeepAlive = 5
Pierog.State = {}
Pierog.State.connected = false;
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.LastConnectionError = 0
Pierog.func = {};
Pierog.func.initialiseMissionData = function()
Pierog.Mission = {}
Pierog.Mission.Hash= {}
Pierog.Mission.Slots = {}
Pierog.Mission.Players = {}
Pierog.Mission.Units = {}
Pierog.Mission.slotsInitialised = false;
Pierog.Mission.lastSent = 0;
Pierog.Mission.LastSentMission = 0;
Pierog.Mission.LastSentSlots = 0;
Pierog.Mission.LastBigUpdate = 0;
end
Pierog.func.ensureSlots = function()
if(not Pierog.Mission.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.Mission.slotsInitialised = true;
end
end
end
Pierog.func.ensureUnitId = function(unitId)
net.log("[Pierog]", "Ensuring unit id: ", 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 = Pierog.DLL.tcpSend(payload);
net.log("[Pierog]", "Sending", payload)
if(connected < 1) and (now > Pierog.Stats.LastConnectionError + Pierog.ReconnectTimeout) then
Pierog.Stats.LastConnectionError = now;
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
end
end
Pierog.func.sendMissionUpdate = function()
local payload = Pierog.Handlers["event"]("mission")
payload["mission"] = DCS.getCurrentMission()["mission"]
payload["missionName"] = DCS.getMissionName()
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_arg1, log_arg2)
-- Logs events messages
local data = Pierog.Handlers["event"]("customLog");
data['log_type']= log_type
data['log_arg_1']= log_arg1
data['log_arg_2']= log_arg2
data['log_content']=log_content
data['log_missionhash']=Pierog.Mission.Hash
Pierog.func.sendData(net.lua2json(data));
Pierog.AddLog("Sending event data, event: " .. log_type .. ", arg1:" .. log_arg1 .. ", arg2:" .. log_arg2 .. ", content: " .. log_content,1)
end
Pierog.onMissionLoadEnd = function()
Pierog.func.initialiseMissionData();
Pierog.Mission.Hash=Pierog.generate_mission_hash();
Pierog.DLL.markMissionStart(Pierog.Mission.Hash);
net.log("[Pierog]", "Mission loaded", Pierog.Mission.Hash);
end
--- ################################ Event callbacks ################################
Pierog.onSimulationStart = function()
Pierog.func.initialiseMissionData();
Pierog.Mission.Hash=Pierog.generate_mission_hash();
Pierog.DLL.markMissionStart(Pierog.Mission.Hash);
net.log("[Pierog]", "markMissionStart", Pierog.Mission.Hash);
Pierog.LogEvent("SimStart","Mission " .. Pierog.Mission.Hash .. " started",nil,nil);
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.Mission.LastBigUpdate < 1 or now > Pierog.Mission.LastBigUpdate + Pierog.bigUpdatesSeconds) then
--net.log("[Pierog]", "Big update evaluation at", now, ", Pierog.Mission.LastSentSlots", Pierog.Mission.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.Mission.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.Mission.LastSentSlots = now;
Pierog.Mission.LastBigUpdate = now;
end
elseif(now > Pierog.Mission.LastSentSlots + Pierog.refreshSlotsSeconds) then
--net.log("[Pierog]", "Slots")
Pierog.func.sendSlotUpdate();
Pierog.Mission.LastSentSlots = now;
Pierog.Mission.LastBigUpdate = now;
end
if (now > Pierog.Mission.LastSentMission) then
--net.log("[Pierog]", "Mission")
Pierog.func.sendMissionUpdate()
Pierog.Mission.LastSentMission = now
Pierog.Mission.LastBigUpdate = now
end
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.onPlayerTryConnect= function(address, name, ucid, id)
net.log("[Pierog]", "onPlayerTryConnect", address, name, ucid, id);
return true;
end
Pierog.onGameEvent = function(eventName, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
local argCount = Pierog.HandlerArgCounts[eventName];
local handler = Pierog.Handlers[eventName];
--net.log("[Pierog]", 'Event', 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)
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")
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

55
01_DCS/Scripts/notes.md Normal file
View File

@@ -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.

View File

@@ -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

View File

@@ -1,12 +1,18 @@
# CMakeList.txt : CMake project for perun_dll, include source and define
# project specific logic here.
#
cmake_minimum_required (VERSION 3.8)
cmake_minimum_required(VERSION 3.17)
project ("parent")
set(CMAKE_CXX_STANDARD 20)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.12.25835)
set(CMAKE_CXX20_STANDARD_COMPILE_OPTION "-std:c++latest")
set(CMAKE_CXX20_EXTENSION_COMPILE_OPTION "-std:c++latest")
endif()
add_subdirectory(lua-5.1.5)
add_subdirectory(perun)
add_subdirectory(pierog)
add_subdirectory(experimental)

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.17)
project(experimental)
set(CMAKE_CXX_STANDARD 20)
set (EXPERIMENTAL_SOURCES
"src/main.cpp"
)
add_executable(experimental ${EXPERIMENTAL_SOURCES})
include_directories(${pierog_SOURCE_DIR})
target_link_libraries(
experimental
ws2_32
pierog
)
target_include_directories (pierog PUBLIC pierog)
#set_target_properties(main PROPERTIES OUTPUT_NAME "pierog")

View File

@@ -28,7 +28,7 @@ set (LUA_RUNTIME_SOURCES
"src/lua.h"
"src/lualib.h"
"src/lzio.c" "src/lzio.h"
)
../experimental/src/main.cpp)
add_library( lua-5.1.5 ${LUA_RUNTIME_SOURCES} )

View File

@@ -44,7 +44,7 @@ void luaK_nil (FuncState *fs, int from, int n) {
if (GET_OPCODE(*previous) == OP_LOADNIL) {
int pfrom = GETARG_A(*previous);
int pto = GETARG_B(*previous);
if (pfrom <= from && from <= pto+1) { /* can connect both? */
if (pfrom <= from && from <= pto+1) { /* can _connect both? */
if (from+n-1 > pto)
SETARG_B(*previous, from+n-1);
return;

View File

@@ -91,7 +91,7 @@
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua"
#define LUA_CPATH_DEFAULT \
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
".\\?.bin;" LUA_CDIR"?.bin;" LUA_CDIR"loadall.bin"
#else
#define LUA_ROOT "/usr/local/"

View File

@@ -1,23 +0,0 @@
cmake_minimum_required(VERSION 3.17)
project(perun)
set(CMAKE_CXX_STANDARD 20)
set (PERUN_DLL_SOURCES
"src/library.h"
"src/library.cpp"
"src/Connection.h"
"src/Connecton.cpp"
)
include (GenerateExportHeader)
add_library(perun SHARED ${PERUN_DLL_SOURCES})
target_link_libraries(
perun
lua-5.1.5
ws2_32
)
target_include_directories (perun PUBLIC)
#set_target_properties(main PROPERTIES OUTPUT_NAME "perun")

View File

@@ -1,48 +0,0 @@
#ifndef PERUN_CONNECTION_H
#define PERUN_CONNECTION_H
#include "winsock.h"
#include <string>
#include <queue>
#include <mutex>
#include <fstream>
#include "library.h"
enum enumConnectionState {
DISCONNECTED,
CONNECTED,
};
class SocketWrapper {
public:
SocketWrapper();
~SocketWrapper();
void disconnect();
void createConnection(std::string* host, const int* port);
void enqueueForSending(std::string* payload);
int getAndResetReconnected();
int getFlagConnected();
private:
SOCKET tcpSocket;
std::string* tcpHost;
int tcpPort;
int flagReconnected = 0;
volatile enumConnectionState connectionState = DISCONNECTED;
std::queue<std::string*> dataBuffer;
std::deque<std::string*> sendQueue;
std::mutex mutexLock;
void reconnect();
void tcpConnect();
};
#endif

View File

@@ -1,124 +0,0 @@
#include "Connection.h"
SocketWrapper::SocketWrapper() {
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
SocketWrapper::~SocketWrapper() {
}
int SocketWrapper::getAndResetReconnected() {
int result = this->flagReconnected;
this->flagReconnected = 0;
return result;
}
int SocketWrapper::getFlagConnected() {
return this->connectionState;
}
void SocketWrapper::tcpConnect() {
// Create socket address object from TCP port and host
SOCKADDR_IN socketAddress;
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(u_short(this->tcpPort));
socketAddress.sin_addr.s_addr = *((unsigned long*)gethostbyname(this->tcpHost->c_str())->h_addr);
if (connect(tcpSocket, (sockaddr*)&socketAddress, sizeof(SOCKADDR_IN)) == 0) {
this->connectionState = CONNECTED;
this->flagReconnected = 1;
}
}
void SocketWrapper::createConnection(std::string* host, const int* port) {
// TCP connection - ConnectTo
this->tcpHost = host;
this->tcpPort = *port;
tcpConnect();
// Create new thread
std::thread thread_object([this]() {
// TCP sending loop
bool nothingToSend = false;
while (true) {
// Endless loop (will run after main dll thread is active)
if (connectionState == CONNECTED && mutexLock.try_lock()) {
if (sendQueue.empty()) {
nothingToSend = true;
} else {
// Payload in queue
auto payload = sendQueue.front();
int length = payload->length();
int bytesSent = send(tcpSocket, payload->c_str(), payload->length(), 0);
if (bytesSent == payload->length()) {
// All payload was sent
sendQueue.pop_front();
delete payload;
} else {
// Remaining paylad
if (bytesSent > 0) {
// Send remaining bytes
auto shortened = payload->substr(bytesSent, length - bytesSent);
sendQueue.pop_front();
sendQueue.push_front(&shortened);
delete payload;
} else {
// Payload was not sent - handle error
switch (WSAGetLastError()) {
// Connection was reset
case WSAECONNRESET:
// Connection aborted
case WSAECONNABORTED:
// Connection was closed
case WSAESHUTDOWN:
connectionState = DISCONNECTED;
reconnect();
}
}
}
}
mutexLock.unlock();
} else {
// Not connected
reconnect();
}
// sleep longer if nothing to send
if (nothingToSend) { Sleep(100); } else { Sleep(10); }
}
});
thread_object.detach(); // Detach TCP thread from main thread
}
void SocketWrapper::reconnect() {
if (connectionState == DISCONNECTED) {
disconnect();
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Reset socket
}
tcpConnect();
}
void SocketWrapper::disconnect() {
// TCP connection - Disconnect
closesocket(tcpSocket);
connectionState = DISCONNECTED;
}
void SocketWrapper::enqueueForSending(std::string* payload) {
if (mutexLock.try_lock()) {
while (!dataBuffer.empty()) {
// Shift buffer to queue
sendQueue.push_back(dataBuffer.front());
dataBuffer.pop();
}
sendQueue.push_back(payload);
mutexLock.unlock();
}
else {
dataBuffer.push(payload);
}
}

View File

@@ -1,55 +0,0 @@
#include "library.h"
static SocketWrapper tcpConnection;
static int appStartHook(lua_State* luaState) {
// Starting the app - prepare
lua_pushinteger(luaState, 1); // First return value: confirmation that app was started
return (1); // Set return to number of arguments returned
}
static int appEndHook(lua_State* luaState) {
// Closing the app - clean up
tcpConnection.disconnect();
return (0); // No return values
}
static int tcpConnect(lua_State* luaState) {
// Connect to the TCP socket
auto *host = new std::string(lua_tolstring(luaState, 1, 0));
const int *port = new int(lua_tointeger(luaState, 2));
tcpConnection.createConnection(host, port);
return (0); // No return values
}
static int tcpSend(lua_State* luaState) {
// Send frame over TCP socket
tcpConnection.enqueueForSending(new std::string(lua_tolstring(luaState, 1, 0)));
lua_pushinteger(luaState, tcpConnection.getFlagConnected()); // First return value: information if there is TCP connection
lua_pushinteger(luaState, tcpConnection.getAndResetReconnected()); // Secound return value: information if there was recent reconnection to TCP server
return (2); // Set return to number of arguments returned
}
extern "C" int __declspec(dllexport) luaopen_perun(lua_State * L) {
static const luaL_Reg Map[] = {
{"StartOfApp", appStartHook}, // Called at the begining of the session
{"EndOfApp", appEndHook }, // Called at the end of the session out of LuaExportStop
{"tcpSend", tcpSend}, // Called to send data from lua over TCP
{"tcpConnect", tcpConnect}, // Create connection
{ NULL, NULL }
};
// Register the list of functions for lua
luaL_register(L, "perun", Map);
return 1;
}

View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.17)
project(pierog)
set(CMAKE_CXX_STANDARD 20)
set(PIEROG_DLL_SOURCES
"src/library.h"
"src/library.cpp"
src/RotatingFileOutput.cpp
src/SocketOutput.cpp
src/DataDistributor.cpp
src/RotatingFileOutput.h
src/SocketOutput.h
src/DataDistributor.h)
include(GenerateExportHeader)
add_library(pierog SHARED ${PIEROG_DLL_SOURCES})
target_link_libraries(
pierog
lua-5.1.5
ws2_32
)
target_include_directories(pierog PUBLIC ${PIEROG_DLL_SOURCES})

View File

@@ -0,0 +1,102 @@
#include "DataDistributor.h"
#include <utility>
#include <ostream>
DataDistributor::DataDistributor(std::string logPath,
std::string host,
int port) {
fileOutput = new RotatingFileOutput(std::move(logPath));
socketOutput = new SocketOutput(std::move(host), port);
}
DataDistributor::~DataDistributor() = default;
void DataDistributor::start() {
if(running) {
return;
}
std::thread thread_object([this]() {
long KEEP_ALIVE = 1000;
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<std::chrono::milliseconds>(now - lastSent);
if(delay.count() > KEEP_ALIVE) {
sendQueue.push_front(new std::string(" "));
} else {
nothingToSend = true;
}
} else {
auto payload = sendQueue.front();
int sent = socketOutput->write(payload);
lastSent = std::chrono::system_clock::now();
if(sent > 0) {
everSentViaSocket = true;
sendQueue.pop_front();
if(sent == payload->length()) {
delete payload;
} else {
auto shortened = payload->substr(sent, payload->length() - sent);
sendQueue.push_front(&shortened);
delete payload;
}
} else {
// failed to send
}
}
lock.unlock();
}
if(nothingToSend) {
Sleep(100);
}
}
});
thread_object.detach();
shouldRun = true;
running = true;
}
void DataDistributor::stop() {
shouldRun = false;
}
void DataDistributor::enqueueForSending(std::string *payload) {
fileOutput->write(payload);
if (lock.try_lock()) {
while (!dataBuffer.empty()) {
// Shift buffer to queue
sendQueue.push_back(dataBuffer.front());
dataBuffer.pop_front();
}
sendQueue.push_back(payload);
lock.unlock();
}
else {
dataBuffer.push_back(payload);
}
}
void DataDistributor::markNewRecording() {
fileOutput->markNewRecording();
if(!everSentViaSocket) {
lock.lock();
sendQueue.clear();
dataBuffer.clear();
lock.unlock();
}
}
int DataDistributor::isConnected() {
return socketOutput->isConnected();
}

View File

@@ -0,0 +1,40 @@
#ifndef PARENT_DATADISTRIBUTOR_H
#define PARENT_DATADISTRIBUTOR_H
#include <string>
#include <queue>
#include "SocketOutput.h"
#include "RotatingFileOutput.h"
class DataDistributor {
public:
DataDistributor(std::string logPath,
std::string host,
int port);
virtual ~DataDistributor();
void enqueueForSending(std::string* payload);
void markNewRecording();
void start();
void stop();
int isConnected();
private:
std::atomic<boolean> shouldRun = true;
std::atomic<boolean> running = false;
std::atomic<boolean> everSentViaSocket = false;
std::deque<std::string*> dataBuffer;
std::deque<std::string*> sendQueue;
std::mutex lock;
std::chrono::time_point<std::chrono::system_clock> lastSent = std::chrono::system_clock::now();
SocketOutput* socketOutput;
RotatingFileOutput* fileOutput;
};
#endif //PARENT_DATADISTRIBUTOR_H

View File

@@ -0,0 +1,41 @@
#include "RotatingFileOutput.h"
RotatingFileOutput::~RotatingFileOutput() = default;
RotatingFileOutput::RotatingFileOutput(std::string outputPath): path(std::move(outputPath)) {
}
void RotatingFileOutput::markNewRecording() {
if(outputFile != nullptr && outputFile->good()) {
outputFile->flush();
outputFile->close();
delete outputFile;
outputFile = nullptr;
}
}
void RotatingFileOutput::write(std::string *payload) {
if (outputFile == nullptr) {
std::string fileName = generateFileName();
outputFile = new std::ofstream(fileName, std::ofstream::app | std::ios::out);
}
outputFile->write(payload->c_str(), payload->length());
bytesWritten += (long) payload->length();
if (payload->length() > 50) {
outputFile->flush();
}
}
std::string RotatingFileOutput::generateFileName() {
auto const now = std::chrono::system_clock::now();
auto const gmt = std::chrono::locate_zone("Etc/GMT");
auto const filename = std::format("pierog.{:%FT_%H%M%S}.log",
std::chrono::zoned_time{gmt, floor<std::chrono::milliseconds>(now)});
std::filesystem::path dir(this->path);
std::filesystem::path file(filename);
return (dir / file).string();
}

View File

@@ -0,0 +1,27 @@
#ifndef PARENT_ROTATINGFILEOUTPUT_H
#define PARENT_ROTATINGFILEOUTPUT_H
#include <mutex>
#include <fstream>
#include <iostream>
#include <filesystem>
class RotatingFileOutput {
public:
RotatingFileOutput(std::string outputPath);
virtual ~RotatingFileOutput();
void markNewRecording();
void write(std::string* payload);
private:
const std::string path;
std::atomic<long long> bytesWritten;
std::ofstream * outputFile = nullptr;
std::string generateFileName();
};
#endif //PARENT_ROTATINGFILEOUTPUT_H

View File

@@ -0,0 +1,56 @@
#include "SocketOutput.h"
SocketOutput::SocketOutput(std::string host,
const int port): tcpHost(std::move(host)), tcpPort(port) {
address = new SOCKADDR_IN;
address->sin_family = AF_INET;
address->sin_port = htons(u_short(this->tcpPort));
address->sin_addr.s_addr = *((unsigned long*)gethostbyname(this->tcpHost.c_str())->h_addr);
}
SocketOutput::~SocketOutput() = default;
int SocketOutput::write(std::string *payload) {
if(!(isConnected() || _connect())) {
return 0;
}
int bytesSent = send(tcpSocket, payload->c_str(), payload->length(), 0);
if(bytesSent > 0) {
return bytesSent;
} else {
switch (WSAGetLastError()) {
case WSAECONNRESET: // Connection reset
case WSAECONNABORTED: // Connection aborted
case WSAESHUTDOWN: // Connection closed
disconnect();
}
return 0;
}
}
bool SocketOutput::isConnected() {
return connectionState == CONNECTED;
}
bool SocketOutput::_connect() {
if(tcpSocket == INVALID_SOCKET) {
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
if(connectionState != CONNECTED) {
if (connect(tcpSocket, (sockaddr *) address, sizeof(SOCKADDR_IN)) == 0) {
connectionState = CONNECTED;
return true;
}
}
return false;
}
void SocketOutput::disconnect() {
closesocket(tcpSocket);
tcpSocket = INVALID_SOCKET;
connectionState = DISCONNECTED;
}

View File

@@ -0,0 +1,38 @@
#ifndef PARENT_SOCKETOUTPUT_H
#define PARENT_SOCKETOUTPUT_H
#include "winsock.h"
#include <string>
#include <atomic>
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

View File

@@ -0,0 +1,99 @@
#include "library.h"
#include "DataDistributor.h"
#include <format>
#include <filesystem>
static DataDistributor * dataDistributor = nullptr;
static std::string *startingDelimiter = nullptr;
static std::string *endingDelimiter = nullptr;
static std::string lastObservedHash = std::string("");
/* this method exists for comments */
static int valuesToReturn(int input) {
return input;
}
static int appStartHook(lua_State* luaState) {
// Starting the app - prepare
if(dataDistributor == nullptr) {
int i = 1;
const std::string path = std::string(lua_tolstring(luaState, i++, 0));
const std::string host = std::string(lua_tolstring(luaState, i++, 0));
const int port = (int) lua_tointeger(luaState, i++);
dataDistributor = new DataDistributor(path, host, port);
dataDistributor->start();
}
lua_pushinteger(luaState, 1); // First return value: confirmation that app was started
return valuesToReturn(1);
}
static int appEndHook(lua_State* luaState) {
// Closing the app - clean up
if(dataDistributor != nullptr) {
dataDistributor->stop();
}
return valuesToReturn(0);
}
static int setDelimiters(lua_State* luaState) {
startingDelimiter = new std::string(lua_tolstring(luaState, 1, 0));
endingDelimiter = new std::string(lua_tolstring(luaState, 2, 0));
return valuesToReturn(0);
}
static int markMissionStart(lua_State* luaState) {
std::string missionHash = std::string(lua_tolstring(luaState, 1, 0));
if(dataDistributor != nullptr && missionHash != lastObservedHash) {
dataDistributor->markNewRecording();
lastObservedHash = missionHash;
}
return valuesToReturn(0);
}
static int tcpSend(lua_State* luaState) {
// Send frame over TCP socket
if(dataDistributor != nullptr) {
if(startingDelimiter != nullptr) {
dataDistributor->enqueueForSending(new std::string(*startingDelimiter));
}
dataDistributor->enqueueForSending(new std::string(lua_tolstring(luaState, 1, 0)));
if(endingDelimiter != nullptr) {
dataDistributor->enqueueForSending(new std::string(*endingDelimiter));
}
lua_pushinteger(luaState,
dataDistributor->isConnected()); // First return value: information if there is TCP connection
} else {
lua_pushinteger(luaState, 0);
}
return valuesToReturn(1);
}
extern "C" int __declspec(dllexport) luaopen_pierog(lua_State * L) {
static const luaL_Reg Map[] = {
{"StartOfApp", appStartHook}, // Called at the begining of the session
{"EndOfApp", appEndHook }, // Called at the end of the session out of LuaExportStop
{"tcpSend", tcpSend}, // Called to send data from lua over TCP
{"delimiters", setDelimiters },
{"markMissionStart", markMissionStart },
{ NULL, NULL }
};
// Register the list of functions for lua
luaL_register(L, "pierog", Map);
return 1;
}

View File

@@ -12,6 +12,5 @@ extern "C" {
#include <fstream>
#include <chrono>
#include <queue>
#include "Connection.h"
#endif //PERUN_LIBRARY_H

View File

@@ -14,7 +14,7 @@ CREATE TABLE IF NOT EXISTS `pe_Config` (
`pe_Config_id` int(11) NOT NULL,
`pe_Config_payload` varchar(255) DEFAULT NULL,
PRIMARY KEY (`pe_Config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES
(1, 'v0.12.1');
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
PRIMARY KEY (`pe_DataMissionHashes_id`),
UNIQUE KEY `UNIQUE_hash` (`pe_DataMissionHashes_hash`),
KEY `pe_DataMissionHashes_instance` (`pe_DataMissionHashes_instance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_DataPlayers`;
CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
@@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
`pe_DataPlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`pe_DataPlayers_id`),
UNIQUE KEY `UNIQUE_UCID` (`pe_DataPlayers_ucid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_DataRaw`;
CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
@@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`),
KEY `pe_dataraw_type_pe_dataraw_instance` (`pe_dataraw_type`,`pe_dataraw_instance`),
KEY `pe_dataraw_updated` (`pe_dataraw_updated`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_DataTypes`;
CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
@@ -59,14 +59,14 @@ CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
`pe_DataTypes_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`pe_DataTypes_id`),
UNIQUE KEY `UNIQUE_TYPE_NAME` (`pe_DataTypes_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_LogChat`;
CREATE TABLE IF NOT EXISTS `pe_LogChat` (
`pe_LogChat_id` bigint(20) NOT NULL AUTO_INCREMENT,
`pe_LogChat_id` bigint NOT NULL AUTO_INCREMENT,
`pe_LogChat_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`pe_LogChat_missionhash_id` bigint(20) DEFAULT NULL,
`pe_LogChat_playerid` varchar(100) NOT NULL,
`pe_LogChat_missionhash_id` bigint DEFAULT NULL,
`pe_LogChat_playerid` bigint NOT NULL,
`pe_LogChat_msg` text NOT NULL,
`pe_LogChat_all` varchar(10) NOT NULL,
PRIMARY KEY (`pe_LogChat_id`),
@@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogChat` (
KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`),
KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`),
KEY `pe_LogChat_all` (`pe_LogChat_all`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_LogEvent`;
CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
@@ -90,7 +90,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`),
KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`),
KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_LogLogins`;
CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
@@ -104,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`),
KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`),
KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_LogStats`;
CREATE TABLE IF NOT EXISTS `pe_LogStats` (
@@ -149,7 +149,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogStats` (
KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`),
KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`),
KEY `pe_LogStats_seat` (`pe_LogStats_seat`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`;
DELIMITER $$
CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN
@@ -182,7 +182,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlinePlayers` (
`pe_OnlinePlayers_slot` varchar(255) DEFAULT NULL,
`pe_OnlinePlayers_ucid` varchar(255) DEFAULT NULL,
`pe_OnlinePlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `pe_OnlineStatus`;
CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
@@ -197,7 +197,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
`pe_OnlineStatus_perunversion_winapp` varchar(255) DEFAULT NULL,
`pe_OnlineStatus_perunversion_dcshook` varchar(255) DEFAULT NULL,
`pe_OnlineStatus_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`);