diff --git a/01_DCS/Hooks/Perun.lua b/01_DCS/Hooks/Perun.lua index 3be720b..5fd483a 100644 --- a/01_DCS/Hooks/Perun.lua +++ b/01_DCS/Hooks/Perun.lua @@ -7,20 +7,22 @@ package.cpath = package.cpath..";"..lfs.currentdir().."/LuaSocket/?.dll" -- ###################### SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION ############################# -Perun.RefreshStatus = 15 -- (int) base refresh rate in seconds to send status update (values lower than 60 may affect performance!) -Perun.RefreshMission = 60 -- (int) refresh rate in seconds to send mission information (values lower than 60 may affect performance!) -Perun.TCPTargetPort = 48622 -- (int) TCP port to send data to -Perun.TCPPerunHost = "localhost" -- (string) IP adress of the Perun instance or "localhost" -Perun.Instance = 2 -- (int) Id number of instance (if multiple DCS instances are to run at the same PC) -Perun.JsonStatusLocation = "Scripts\\Json\\" -- (string) folder relative do user's SaveGames DCS folder -> status file updated each RefreshMission -Perun.MOTD_L1 = "Witamy na serwerze Gildia.org !" -- (string) Message send to players connecting the server - Line 1 +Perun.RefreshStatus = 15 -- (int) [default: 60] Base refresh rate in seconds to send status update +Perun.RefreshMission = 60 -- (int) [default: 120] Refresh rate in seconds to send mission information +Perun.TCPTargetPort = 48622 -- (int) [default: 48621] TCP port to send data to +Perun.TCPPerunHost = "localhost" -- (string) [default: "localhost"] IP adress of the Perun instance or "localhost" +Perun.Instance = 2 -- (int) [default: 1] Id number of instance (if multiple DCS instances are to run at the same PC) +Perun.JsonStatusLocation = "Scripts\\Json\\" -- (string) [default: "Scripts\\Json\\"] Folder relative do user's SaveGames DCS folder -> status file updated each RefreshMission +Perun.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 +Perun.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 +Perun.LogServerMessages = 1 -- (int) [0,1 (default)] Set to 1 if you want to log also the chat messages send by server +Perun.MOTD_L1 = "Witamy na serwerze Gildia.org !" -- (string) Message send to players connecting the server - Line 1 Perun.MOTD_L2 = "Wymagamy obecnosci DCS SRS oraz TeamSpeak - szczegoly na forum" -- (string) Message send to players connecting the server - Line 2 -- ###################### END OF SETTINGS - DO NOT MODIFY OUTSIDE THIS SECTION ###################### - -- Variable init -Perun.Version = "v0.8.3" +Perun.Version = "v0.9.0" Perun.StatusData = {} Perun.SlotsData = {} Perun.MissionData = {} @@ -28,108 +30,115 @@ Perun.ServerData = {} Perun.StatData = {} Perun.StatDataLastType = {} Perun.MissionHash="" -Perun.lastSentStatus =0 -Perun.lastSentMission =0 -Perun.lastSentKeepAlive =0 +Perun.lastSentStatus = 0 +Perun.lastSentMission = 0 +Perun.lastSentKeepAlive = 0 +Perun.lastConnectionError = 0 Perun.lastReconnect = 0 +Perun.SendRetries = 3 +Perun.RefreshKeepAlive = 3 Perun.JsonStatusLocation = lfs.writedir() .. Perun.JsonStatusLocation Perun.socket = require("socket") Perun.IsServer = true --DCS.isServer( ) -- TBD looks like DCS API error, always returning True +Perun.ConnectionError = "[Perun] ERROR: Connection broken - contact server admin!" --- ########### Helper function definitions ########### +-- ################################ Helper function definitions ################################ function stripChars(str) - -- remove accents characters from string + -- Hellper functions removes accents characters from string -- via https://stackoverflow.com/questions/50459102/replace-accented-characters-in-string-to-standard-with-lua - tableAccents = {} - tableAccents["à"] = "a" - tableAccents["á"] = "a" - tableAccents["â"] = "a" - tableAccents["ã"] = "a" - tableAccents["ä"] = "a" - tableAccents["ç"] = "c" - tableAccents["è"] = "e" - tableAccents["é"] = "e" - tableAccents["ê"] = "e" - tableAccents["ë"] = "e" - tableAccents["ì"] = "i" - tableAccents["í"] = "i" - tableAccents["î"] = "i" - tableAccents["ï"] = "i" - tableAccents["ñ"] = "n" - tableAccents["ò"] = "o" - tableAccents["ó"] = "o" - tableAccents["ô"] = "o" - tableAccents["õ"] = "o" - tableAccents["ö"] = "o" - tableAccents["ù"] = "u" - tableAccents["ú"] = "u" - tableAccents["û"] = "u" - tableAccents["ü"] = "u" - tableAccents["ý"] = "y" - tableAccents["ÿ"] = "y" - tableAccents["À"] = "A" - tableAccents["Á"] = "A" - tableAccents["Â"] = "A" - tableAccents["Ã"] = "A" - tableAccents["Ä"] = "A" - tableAccents["Ç"] = "C" - tableAccents["È"] = "E" - tableAccents["É"] = "E" - tableAccents["Ê"] = "E" - tableAccents["Ë"] = "E" - tableAccents["Ì"] = "I" - tableAccents["Í"] = "I" - tableAccents["Î"] = "I" - tableAccents["Ï"] = "I" - tableAccents["Ñ"] = "N" - tableAccents["Ò"] = "O" - tableAccents["Ó"] = "O" - tableAccents["Ô"] = "O" - tableAccents["Õ"] = "O" - tableAccents["Ö"] = "O" - tableAccents["Ù"] = "U" - tableAccents["Ú"] = "U" - tableAccents["Û"] = "U" - tableAccents["Ü"] = "U" - tableAccents["Ý"] = "Y" + local _tableAccents = {} + _tableAccents["à"] = "a" + _tableAccents["á"] = "a" + _tableAccents["â"] = "a" + _tableAccents["ã"] = "a" + _tableAccents["ä"] = "a" + _tableAccents["ç"] = "c" + _tableAccents["è"] = "e" + _tableAccents["é"] = "e" + _tableAccents["ê"] = "e" + _tableAccents["ë"] = "e" + _tableAccents["ì"] = "i" + _tableAccents["í"] = "i" + _tableAccents["î"] = "i" + _tableAccents["ï"] = "i" + _tableAccents["ñ"] = "n" + _tableAccents["ò"] = "o" + _tableAccents["ó"] = "o" + _tableAccents["ô"] = "o" + _tableAccents["õ"] = "o" + _tableAccents["ö"] = "o" + _tableAccents["ù"] = "u" + _tableAccents["ú"] = "u" + _tableAccents["û"] = "u" + _tableAccents["ü"] = "u" + _tableAccents["ý"] = "y" + _tableAccents["ÿ"] = "y" + _tableAccents["À"] = "A" + _tableAccents["Á"] = "A" + _tableAccents["Â"] = "A" + _tableAccents["Ã"] = "A" + _tableAccents["Ä"] = "A" + _tableAccents["Ç"] = "C" + _tableAccents["È"] = "E" + _tableAccents["É"] = "E" + _tableAccents["Ê"] = "E" + _tableAccents["Ë"] = "E" + _tableAccents["Ì"] = "I" + _tableAccents["Í"] = "I" + _tableAccents["Î"] = "I" + _tableAccents["Ï"] = "I" + _tableAccents["Ñ"] = "N" + _tableAccents["Ò"] = "O" + _tableAccents["Ó"] = "O" + _tableAccents["Ô"] = "O" + _tableAccents["Õ"] = "O" + _tableAccents["Ö"] = "O" + _tableAccents["Ù"] = "U" + _tableAccents["Ú"] = "U" + _tableAccents["Û"] = "U" + _tableAccents["Ü"] = "U" + _tableAccents["Ý"] = "Y" -- Polish accents - tableAccents["ę"] = "e" - tableAccents["Ę"] = "Ę" - tableAccents["ó"] = "o" - tableAccents["Ó"] = "O" - tableAccents["ą"] = "a" - tableAccents["Ą"] = "A" - tableAccents["ś"] = "s" - tableAccents["Ś"] = "S" - tableAccents["ć"] = "c" - tableAccents["Ć"] = "C" - tableAccents["ż"] = "z" - tableAccents["Ż"] = "Z" - tableAccents["ź"] = "z" - tableAccents["Ź"] = "Z" - tableAccents["ł"] = "l" - tableAccents["Ł"] = "L" + _tableAccents["ę"] = "e" + _tableAccents["Ę"] = "Ę" + _tableAccents["ó"] = "o" + _tableAccents["Ó"] = "O" + _tableAccents["ą"] = "a" + _tableAccents["Ą"] = "A" + _tableAccents["ś"] = "s" + _tableAccents["Ś"] = "S" + _tableAccents["ć"] = "c" + _tableAccents["Ć"] = "C" + _tableAccents["ż"] = "z" + _tableAccents["Ż"] = "Z" + _tableAccents["ź"] = "z" + _tableAccents["Ź"] = "Z" + _tableAccents["ł"] = "l" + _tableAccents["Ł"] = "L" - -- TBD additonal characters + -- TBD additonal characters for other languages - normalizedString = '' - - for strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do - if tableAccents[strChar] ~= nil then - normalizedString = normalizedString..tableAccents[strChar] + -- Check string and replace special chars via replacement table + local _normalizedString = '' + for _strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do + if _tableAccents[_strChar] ~= nil then + -- Replace char + _normalizedString = _normalizedString.._tableAccents[_strChar] else - normalizedString = normalizedString..strChar + -- No need to replace + _normalizedString = _normalizedString.._strChar end end - return normalizedString + return _normalizedString end Perun.GetCategory = function(id) - -- Returns object category basing on ID - -- Helper function via https://pastebin.com/GUAXrd2U TBD: rewrite + -- Helper function returns object category basing on I via https://pastebin.com/GUAXrd2U TBD: rewrite local _killed_target_category = DCS.getUnitTypeAttribute(id, "category") - if _killed_target_category == nil then + + -- Below, simple hack to get the propper category when DCS API is not returning correct value + if _killed_target_category == nil then local _killed_target_cat_check_ship = DCS.getUnitTypeAttribute(id, "DeckLevel") local _killed_target_cat_check_plane = DCS.getUnitTypeAttribute(id, "WingSpan") if _killed_target_cat_check_ship ~= nil and _killed_target_cat_check_plane == nil then @@ -145,90 +154,421 @@ end Perun.SideID2Name = function(id) -- Helper function returns side name per side (coalition) id - local sides = { + local _sides = { [0] = 'SPECTATOR', [1] = 'RED', [2] = 'BLUE', + [3] = 'NEUTRAL', -- TBD check once this is released in DCS } - return sides[id] + return _sides[id] end -Perun.UpdateStatusPart = function(part_id, data_package) - -- Helper for status update container - Perun.StatusData[part_id] = data_package -end - --- ########### Main code ########### - -Perun.AddLog = function(text) +Perun.AddLog = function(text,LogLevel) -- Adds logs to DCS.log file - net.log("Perun : ".. text) + LogLevel = LogLevel or 1 + if Perun.DebugMode >= LogLevel then + net.log("[Perun] ".. text) + end end -Perun.UpdateJsonStatus = function() - -- Updates status json file - TempData={} - TempData["1"]=Perun.ServerData - TempData["2"]=Perun.StatusData - TempData["3"]=Perun.SlotsData - -- TempData["4"]=Perun.MissionData -- TBD: hangs for some large missions - - _temp=net.lua2json(TempData) - - perun_export = io.open(Perun.JsonStatusLocation .. "perun_status_data.json", "w") - perun_export:write(_temp .. "\n") - perun_export:close() - Perun.AddLog("Updated JSON") +Perun.GenerateMissionHash = function() + -- Generates unique simulation mission hash + return DCS.getMissionName( ).."@".. Perun.Instance .. "@" .. Perun.Version .. "@".. os.date('%Y%m%d_%H%M%S') end +Perun.GetMulticrewAllParameters = function (PlayerId) + -- Gets all multicrew parameters + local _result = "" + local _player_slot=net.get_player_info(PlayerId, 'slot') + local _master_type= "?" + local _master_slot = nil + local _sub_slot = nil + + if _player_slot and _player_slot ~= '' and not (string.find(_player_slot, 'red') or string.find(_player_slot, 'blue')) then + -- Player took model + _master_slot = _player_slot + _sub_slot =0 + + if (not tonumber(_player_slot)) then + -- If this is multiseat slot parse master slot and look for seat number + _t_start, _t_end = string.find(_player_slot, '_%d+') + + if _t_start then + -- This is co-player + _master_slot = string.sub(_player_slot, 0 , _t_start -1 ) + _sub_slot = string.sub(_player_slot, _t_start + 1, _t_end ) + end + end + _master_type = DCS.getUnitType(_master_slot) + + elseif string.find(_player_slot, 'red') or string.find(_player_slot, 'blue') then + -- Deal with the special slots addded by Combined Arms and Spectators + if string.find(_player_slot, 'artillery_commander') then + _master_type = "artillery_commander" + elseif string.find(_player_slot, 'instructor') then + _master_type = "instructor" + elseif string.find(_player_slot, 'forward_observer') then + _master_type = "forward_observer" + elseif string.find(_player_slot, 'observer') then + _master_type = "observer" + end + _master_slot = -1 + _sub_slot = 0 + end + return _master_type,_master_slot,_sub_slot +end + +Perun.GetMulticrewParameter = function (PlayerId,Parameter) + -- Get specific multicrew Parameter + _master_type,_master_slot,_sub_slot = Perun.GetMulticrewAllParameters(PlayerId) + + if Parameter == "mastertype" then + return _master_type + elseif Parameter == "masterslot" then + return _master_slot + elseif Parameter == "subslot" then + return _sub_slot + elseif Parameter == "subtype" then + if _sub_slot == 0 then + return _master_type + else + return _master_type .. "_" .. _sub_slot + end + else + return nil + end +end + +Perun.GetMulticrewCrew = function (owner_playerID) + -- Get all multicrew crew + _master_type,_master_slot,_sub_slot = Perun.GetMulticrewAllParameters(owner_playerID) + + local _crew = {} + table.insert(_crew, owner_playerID) + if _master_type == "F-14B" or _master_type == "Yak-52" or _master_type == "L-39C" or _master_type == "SA342M" or _master_type =="SA342Minigun" or _master_type == "SA342Mistral" or _master_type == "SA342L" then -- TBD add additional multicrew model types + local _owner_side=net.get_player_info(owner_playerID, 'side') + + if _master_slot and _master_slot ~= "" then + -- Search for all players from crew + local _all_players = net.get_player_list() + for PlayerIDIndex, _playerID in ipairs(_all_players) do + local _playerDetails = net.get_player_info( _playerID ) + + if _playerDetails.side == _owner_side and (_playerDetails.slot == _master_slot or _playerDetails.slot == _master_slot .. "_1" or _playerDetails.slot == _master_slot .. "_2" or _playerDetails.slot == _master_slot .. "_3" or _playerDetails.slot == _master_slot .. "_4") and _playerID ~= owner_playerID then + -- Add to crew list + table.insert(_crew, _playerID) + else + -- Do nothing + end + end + end + end + + Perun.AddLog("Multicrew check completed: " .. net.lua2json(_crew),2) + return _crew +end + +Perun.GetMulticrewCrewNames = function (owner_playerID) + -- Get all crew names and return as string (needed for logging purposes) + local _pilots_accounted = Perun.GetMulticrewCrew(owner_playerID) + local _result_text = "" + for _, pilotID in ipairs(_pilots_accounted) do + _result_text= _result_text .. net.get_player_info(pilotID, "name") .. ", " + end + return _result_text +end + +-- ################################ TCP Connection ################################ + Perun.ConnectToPerun = function () -- Connects to Perun server - Perun.AddLog("Connecting to TCP server") + Perun.AddLog("Connecting to TCP server",2) Perun.TCP = assert(Perun.socket.tcp()) Perun.TCP:settimeout(5000) - _, err = Perun.TCP:connect(Perun.TCPPerunHost, Perun.TCPTargetPort) - if err then - Perun.AddLog("ERROR - TCP connection error : " .. err) + _, _err = Perun.TCP:connect(Perun.TCPPerunHost, Perun.TCPTargetPort) + if _err then + -- Could not connect + Perun.AddLog("ERROR - TCP connection error : " .. _err , 1) else - Perun.AddLog("Sucess - connected to TCP server") - -- Perun.TCP:setoption("keepalive") + -- Connected + Perun.AddLog("Success - connected to TCP server",2) Perun.lastReconnect = _now end end Perun.SendToPerun = function(data_id, data_package) -- Prepares and sends data package - -- Prepare data - TempData={} - TempData["type"]=data_id - TempData["payload"]=data_package - TempData["timestamp"]=os.date('%Y-%m-%d %H:%M:%S') - TempData["instance"]=Perun.Instance + local _TempData={} + _TempData["type"]=data_id + _TempData["payload"]=data_package + _TempData["timestamp"]=os.date('%Y-%m-%d %H:%M:%S') + _TempData["instance"]=Perun.Instance - temp=net.lua2json(TempData) - temp="" .. stripChars(temp) .. "" + -- Build TCP frame + local _TCPFrame="" .. stripChars(net.lua2json(_TempData)) .. "" -- TCP Part - sending - intStatus = nil - intTries =0 - err=nil - while intStatus == nil and intTries < 3 do - intStatus, err = Perun.TCP:send(temp) - if err then - Perun.AddLog("Packed not send : " .. data_id .. " , error: " .. err .. ", tries: " .. intTries) + local _intStatus = nil + local _intTries =0 + local _err=nil + local _dropped = true + + -- Try to send a few times (defind in settings section) + while _intStatus == nil and _intTries < Perun.SendRetries do + _intStatus, _err = Perun.TCP:send(_TCPFrame) + if _err then + -- Failure, packet was not send + Perun.AddLog("Packed not send : " .. data_id .. " , error: " .. _err .. ", tries: " .. _intTries,2) Perun.ConnectToPerun() else - Perun.AddLog("Packet send : " .. data_id .. " , tries:" .. intTries) + -- Succes, packet was send + Perun.AddLog("Packet send : " .. data_id .. " , tries:" .. _intTries,2) + _dropped = false end - intTries=intTries+1 - err = nil + _intTries=_intTries+1 end - if err then - Perun.AddLog("ERROR - packed dropped : " .. data_id) + + if _dropped == true then + -- Add information to log file and send chat message to all that Perun connection is broken + Perun.AddLog("ERROR - packed dropped : " .. data_id,1) + local _now = DCS.getRealTime() + if _now > Perun.lastConnectionError + 60 then + -- Informs all players that there is Peron error; below hack for DCS net.send_chat not working + local _all_players = net.get_player_list() + for PlayerIDIndex, _playerID in ipairs(_all_players) do + net.send_chat_to(Perun.ConnectionError , _playerID) + end + -- Reset last error send counter + Perun.lastConnectionError = _now + end end end +-- ################################ Log functions ################################ + +Perun.LogChat = function(playerID,msg,all) + -- Logs chat messages + local _TempData={} + _TempData['player']= net.get_player_info(playerID, "name") + _TempData['msg']=stripChars(msg) + _TempData['all']=all + _TempData['ucid']=net.get_player_info(playerID, 'ucid') + _TempData['datetime']=os.date('%Y-%m-%d %H:%M:%S') + _TempData['missionhash']=Perun.MissionHash + + Perun.AddLog("Sending chat message",2) + Perun.SendToPerun(50,_TempData) +end + +Perun.LogEvent = function(log_type,log_content,log_arg_1,log_arg_2) + -- Logs events messages + local _TempData={} + _TempData['log_type']= log_type + _TempData['log_arg_1']= log_arg_1 + _TempData['log_arg_2']= log_arg_2 + _TempData['log_content']=log_content + _TempData['log_datetime']=os.date('%Y-%m-%d %H:%M:%S') + _TempData['log_missionhash']=Perun.MissionHash + + if log_arg_1 == nil then + log_arg_1 = "null" + end + if log_arg_2 == nil then + log_arg_2 = "null" + end + + Perun.AddLog("Sending event data, event: " .. log_type .. ", arg1:" .. log_arg_1 .. ", arg2:" .. log_arg_2 .. ", content: " .. log_content,2) + Perun.SendToPerun(51,_TempData) +end + +Perun.LogStats = function(playerID) + -- Log player status + local _PlayerStatsTable=Perun.LogStatsGet(playerID) + local _TempData={} + _TempData['stat_data_perun']=_PlayerStatsTable + _TempData['stat_data_type']= _PlayerStatsTable['ps_type']; + _TempData['stat_data_masterslot'] = _PlayerStatsTable['ps_masterslot']; + _TempData['stat_data_subslot'] = _PlayerStatsTable['ps_subslot']; + _TempData['stat_ucid']=net.get_player_info(playerID, 'ucid') + _TempData['stat_name']=net.get_player_info(playerID, 'name') + _TempData['stat_datetime']=os.date('%Y-%m-%d %H:%M:%S') + _TempData['stat_missionhash']=Perun.MissionHash + + Perun.AddLog("Sending stats data",2) + Perun.SendToPerun(52,_TempData) +end + +Perun.LogLogin = function(playerID) + -- Player logged in + local _TempData={} + _TempData['login_ucid']=net.get_player_info(playerID, 'ucid') + _TempData['login_ipaddr']=net.get_player_info(playerID, 'ipaddr') + _TempData['login_name']=net.get_player_info(playerID, 'name') + _TempData['login_datetime']=os.date('%Y-%m-%d %H:%M:%S') + + Perun.AddLog("Sending login event",2) + Perun.SendToPerun(53,_TempData) +end + +--- ################################ Calculate stats ################################ + +Perun.LogStatsCount = function(argPlayerID,argAction) + -- Creates or updates Perun statistics array + local _player_hash=net.get_player_info(argPlayerID, 'ucid')..Perun.GetMulticrewParameter(argPlayerID,"subtype") + + if Perun.StatData[_player_hash] == nil then + -- Create empty element + local _TempData={} + _TempData['ps_type'] = Perun.GetMulticrewParameter(argPlayerID,"subtype") + _TempData['ps_masterslot'] = Perun.GetMulticrewParameter(argPlayerID,"masterslot") + _TempData['ps_subslot'] = Perun.GetMulticrewParameter(argPlayerID,"subslot") + _TempData['ps_pvp'] = 0 + _TempData['ps_deaths'] = 0 + _TempData['ps_ejections'] = 0 + _TempData['ps_crashes'] = 0 + _TempData['ps_teamkills'] = 0 + _TempData['ps_kills_planes'] = 0 + _TempData['ps_kills_helicopters'] = 0 + _TempData['ps_kills_air_defense'] = 0 + _TempData['ps_kills_armor'] = 0 + _TempData['ps_kills_unarmed'] = 0 + _TempData['ps_kills_infantry'] = 0 + _TempData['ps_kills_ships'] = 0 + _TempData['ps_kills_fortification'] = 0 + _TempData['ps_kills_other'] = 0 + _TempData['ps_airfield_takeoffs'] = 0 + _TempData['ps_airfield_landings'] = 0 + _TempData['ps_ship_takeoffs'] = 0 + _TempData['ps_ship_landings'] = 0 + _TempData['ps_farp_takeoffs'] = 0 + _TempData['ps_farp_landings'] = 0 + _TempData['ps_other_takeoffs'] = 0 + _TempData['ps_other_landings'] = 0 + + Perun.StatData[_player_hash]=_TempData + end + + -- TBD BELLOW SEEMS TO BE OBSOLETE, CHECK AND DELETE + if argType ~= nil then + Perun.StatData[_player_hash]['ps_type']=Perun.GetMulticrewParameter(argPlayerID,"subtype"); + Perun.StatDataLastType[net.get_player_info(argPlayerID, 'ucid')]=_player_hash + else + -- Do nothing + end + -- TBD ABOVE SEEMS TO BE OBSOLETE, CHECK AND DELETE + + if argAction == "eject" then + Perun.StatData[_player_hash]['ps_ejections']=Perun.StatData[_player_hash]['ps_ejections']+1 + elseif argAction == "pilot_death" then + if DCS.getModelTime() > Perun.MissionStartNoDeathWindow then + -- we do not track deaths during mission startup due to spawning issues + Perun.StatData[_player_hash]['ps_deaths']=Perun.StatData[_player_hash]['ps_deaths']+1 + end + elseif argAction == "friendly_fire" then + Perun.StatData[_player_hash]['ps_teamkills']=Perun.StatData[_player_hash]['ps_teamkills']+1 + elseif argAction == "crash" then + Perun.StatData[_player_hash]['ps_crashes']=Perun.StatData[_player_hash]['ps_crashes']+1 + elseif argAction == "landing_FARP" then + Perun.StatData[_player_hash]['ps_farp_landings']=Perun.StatData[_player_hash]['ps_farp_landings']+1 + elseif argAction == "landing_SHIP" then + Perun.StatData[_player_hash]['ps_ship_landings']=Perun.StatData[_player_hash]['ps_ship_landings']+1 + elseif argAction == "landing_AIRFIELD" then + Perun.StatData[_player_hash]['ps_airfield_landings']=Perun.StatData[_player_hash]['ps_airfield_landings']+1 + elseif argAction == "tookoff_FARP" then + Perun.StatData[_player_hash]['ps_farp_takeoffs']=Perun.StatData[_player_hash]['ps_farp_takeoffs']+1 + elseif argAction == "tookoff_SHIP" then + Perun.StatData[_player_hash]['ps_ship_takeoffs']=Perun.StatData[_player_hash]['ps_ship_takeoffs']+1 + elseif argAction == "tookoff_AIRFIELD" then + Perun.StatData[_player_hash]['ps_airfield_takeoffs']=Perun.StatData[_player_hash]['ps_airfield_takeoffs']+1 + elseif argAction == "kill_Planes" then + Perun.StatData[_player_hash]['ps_kills_planes']=Perun.StatData[_player_hash]['ps_kills_planes']+1 + elseif argAction == "kill_Helicopters" then + Perun.StatData[_player_hash]['ps_kills_helicopters']=Perun.StatData[_player_hash]['ps_kills_helicopters']+1 + elseif argAction == "kill_Ships" then + Perun.StatData[_player_hash]['ps_kills_ships']=Perun.StatData[_player_hash]['ps_kills_ships']+1 + elseif argAction == "kill_Air_Defence" then + Perun.StatData[_player_hash]['ps_kills_air_defense']=Perun.StatData[_player_hash]['ps_kills_air_defense']+1 + elseif argAction == "kill_Unarmed" then + Perun.StatData[_player_hash]['ps_kills_unarmed']=Perun.StatData[_player_hash]['ps_kills_unarmed']+1 + elseif argAction == "kill_Armor" then + Perun.StatData[_player_hash]['ps_kills_armor']=Perun.StatData[_player_hash]['ps_kills_armor']+1 + elseif argAction == "kill_Infantry" then + Perun.StatData[_player_hash]['ps_kills_infantry']=Perun.StatData[_player_hash]['ps_kills_infantry']+1 + elseif argAction == "kill_Fortification" then + Perun.StatData[_player_hash]['ps_kills_fortification']=Perun.StatData[_player_hash]['ps_kills_fortification']+1 + elseif argAction == "kill_Other" then + Perun.StatData[_player_hash]['ps_kills_other']=Perun.StatData[_player_hash]['ps_kills_other']+1 + elseif argAction == "kill_PvP" then + Perun.StatData[_player_hash]['ps_pvp']=Perun.StatData[_player_hash]['ps_pvp']+1 + elseif argAction == "landing_OTHER" then + Perun.StatData[_player_hash]['ps_other_landings']=Perun.StatData[_player_hash]['ps_other_landings']+1 + elseif argAction == "tookoff_OTHER" then + Perun.StatData[_player_hash]['ps_other_takeoffs']=Perun.StatData[_player_hash]['ps_other_takeoffs']+1 + end + + -- Always update slots + Perun.StatData[_player_hash]['ps_masterslot'] = Perun.GetMulticrewParameter(argPlayerID,"masterslot") + Perun.StatData[_player_hash]['ps_subslot'] = Perun.GetMulticrewParameter(argPlayerID,"subslot") + + Perun.AddLog("Stats data prepared",2) + Perun.LogStats(argPlayerID); +end + +Perun.LogStatsCountCrew = function (MasterPilotID,ActionType) + -- Change stats for the whole crew + local _pilots_accounted = Perun.GetMulticrewCrew(MasterPilotID) + for _, pilotID in ipairs(_pilots_accounted) do + Perun.LogStatsCount(pilotID,ActionType) + end +end +-- ################################ Data preparation ################################ + +Perun.LogStatsGet = function(playerID) + -- Gets Perun statistics array per player TBD rewrite + local next = next -- Make next function local - this improves performance TBD + local _player_hash = nil + + if next(Perun.StatDataLastType) == nil then + -- Array is empty + _player_hash=net.get_player_info(playerID, 'ucid')..Perun.GetMulticrewParameter(playerID,"subtype") + elseif Perun.StatDataLastType[net.get_player_info(playerID, 'ucid')]== nil then + -- Last type entry is empty + _player_hash=net.get_player_info(playerID, 'ucid')..Perun.GetMulticrewParameter(playerID,"subtype") + else + -- Return last type entry + _player_hash=Perun.StatDataLastType[net.get_player_info(playerID, 'ucid')] + end + + local next = next -- Make next function local - this improves performance TBD + if next(Perun.StatData) == nil then + -- Array is empty + Perun.LogStatsCount(playerID,'init') -- Init statistics + end + if Perun.StatData[_player_hash]== nil then + -- Stats for player are empty + Perun.LogStatsCount(playerID,'init') -- Init statistics + end + + Perun.AddLog("Getting stats data",2) + return Perun.StatData[_player_hash]; +end + +Perun.UpdateJsonStatus = function() + -- Updates status json file + local _TempData={} + _TempData["1"]=Perun.ServerData + _TempData["2"]=Perun.StatusData + _TempData["3"]=Perun.SlotsData + -- _TempData["4"]=Perun.MissionData -- TBD: hangs for some large missions + + -- Export data to JSON file + local _perun_export = io.open(Perun.JsonStatusLocation .. "perun_status_data.json", "w") + _perun_export:write(net.lua2json(_TempData) .. "\n") + _perun_export:close() + Perun.AddLog("Updated JSON",2) +end + Perun.UpdateStatus = function() -- Main function for status updates @@ -236,315 +576,100 @@ Perun.UpdateStatus = function() -- Update version data Perun.ServerData['v_dcs_hook']=Perun.Version - -- Update clients data data - _table=net.get_player_list() + -- Update clients data - count connected players + _playerlist=net.get_player_list() _count = 0 - for _ in pairs(_table) do _count = _count + 1 end + for _ in pairs(_playerlist) do _count = _count + 1 end Perun.ServerData['c_players']=_count -- Send + Perun.AddLog("Sending server data",2) Perun.SendToPerun(1,Perun.ServerData) -- Status data - update all subsections - -- 1 - Mission - temp={} - temp['name']=DCS.getMissionName() - temp['modeltime']=DCS.getModelTime() - temp['realtime']=DCS.getRealTime() - temp['pause']=DCS.getPause() - temp['multiplayer']=DCS.isMultiplayer() - temp['theatre'] = Perun.MissionData['mission']['theatre'] - temp['weather'] = Perun.MissionData['mission']['weather'] - Perun.UpdateStatusPart("mission",temp) + -- 1 - Mission data + local _TempData={} + _TempData['name']=DCS.getMissionName() + _TempData['modeltime']=DCS.getModelTime() + _TempData['realtime']=DCS.getRealTime() + _TempData['pause']=DCS.getPause() + _TempData['multiplayer']=DCS.isMultiplayer() + _TempData['theatre'] = Perun.MissionData['mission']['theatre'] + _TempData['weather'] = Perun.MissionData['mission']['weather'] + Perun.StatusData["mission"] = _TempData - -- 2 - Players - _temp = net.get_player_list() - _temp2={} - for k, i in ipairs(_temp) do - _temp2[k]=net.get_player_info(i) + -- 2 - Players data + _PlayersTable={} + for _k, _i in ipairs(_playerlist) do + _PlayersTable[_k]=net.get_player_info(_i) end - Perun.UpdateStatusPart("players",_temp2) + Perun.StatusData["players"] = _PlayersTable -- Send + Perun.AddLog("Sending status data",2) Perun.SendToPerun(2,Perun.StatusData) -- Update slots data Perun.SlotsData['coalitions']=DCS.getAvailableCoalitions() Perun.SlotsData['slots']={} - for j, i in pairs(Perun.SlotsData['coalitions']) do - Perun.SlotsData['slots'][j]=DCS.getAvailableSlots(j) + -- Build up slot table + for _j, _i in pairs(Perun.SlotsData['coalitions']) do + Perun.SlotsData['slots'][_j]=DCS.getAvailableSlots(_j) - for sj, si in pairs(Perun.SlotsData['slots'][j]) do - Perun.SlotsData['slots'][j][sj]['countryName']= nil - Perun.SlotsData['slots'][j][sj]['onboard_num']= nil - Perun.SlotsData['slots'][j][sj]['groupSize']= nil - Perun.SlotsData['slots'][j][sj]['groupName']= nil - Perun.SlotsData['slots'][j][sj]['callsign']= nil - Perun.SlotsData['slots'][j][sj]['task']= nil - Perun.SlotsData['slots'][j][sj]['airdromeId']= nil - Perun.SlotsData['slots'][j][sj]['helipadName']= nil + for _sj, _si in pairs(Perun.SlotsData['slots'][_j]) do + Perun.SlotsData['slots'][_j][_sj]['countryName']= nil + Perun.SlotsData['slots'][_j][_sj]['onboard_num']= nil + Perun.SlotsData['slots'][_j][_sj]['groupSize']= nil + Perun.SlotsData['slots'][_j][_sj]['groupName']= nil + Perun.SlotsData['slots'][_j][_sj]['callsign']= nil + Perun.SlotsData['slots'][_j][_sj]['task']= nil + Perun.SlotsData['slots'][_j][_sj]['airdromeId']= nil + Perun.SlotsData['slots'][_j][_sj]['helipadName']= nil end - end + -- Send + Perun.AddLog("Sending slots data",2) Perun.SendToPerun(3,Perun.SlotsData) end - Perun.UpdateMission = function() -- Main function for mission information updates + Perun.AddLog("Updating mission data",2) Perun.MissionData=DCS.getCurrentMission() -- Perun.SendToPerun(4,Perun.MissionData) -- TBD can cause data transmission troubles + Perun.AddLog("Mission data updated",2) end -Perun.LogChat = function(playerID,msg,all) - -- Logs chat messages - - data={} - data['player']= net.get_player_info(playerID, "name") - data['msg']=stripChars(msg) - data['all']=all - data['ucid']=net.get_player_info(playerID, 'ucid') - data['datetime']=os.date('%Y-%m-%d %H:%M:%S') - data['missionhash']=Perun.MissionHash - - Perun.SendToPerun(50,data) -end - -Perun.LogEvent = function(log_type,log_content,log_arg_1,log_arg_2) - -- Logs events messages - - data={} - 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_datetime']=os.date('%Y-%m-%d %H:%M:%S') - data['log_missionhash']=Perun.MissionHash - - Perun.SendToPerun(51,data) -end - -Perun.LogStatsCount = function(argPlayerID,argAction,argType) - -- Creates or updates Perun statistics array - _ucid=net.get_player_info(argPlayerID, 'ucid')..argType - - if Perun.StatData[_ucid] == nil then - -- Create empty element - _temp={} - _temp['ps_type'] = argType - _temp['ps_pvp'] = 0 - _temp['ps_deaths'] = 0 - _temp['ps_ejections'] = 0 - _temp['ps_crashes'] = 0 - _temp['ps_teamkills'] = 0 - _temp['ps_kills_planes'] = 0 - _temp['ps_kills_helicopters'] = 0 - _temp['ps_kills_air_defense'] = 0 - _temp['ps_kills_armor'] = 0 - _temp['ps_kills_unarmed'] = 0 - _temp['ps_kills_infantry'] = 0 - _temp['ps_kills_ships'] = 0 - _temp['ps_kills_fortification'] = 0 - _temp['ps_kills_other'] = 0 - _temp['ps_airfield_takeoffs'] = 0 - _temp['ps_airfield_landings'] = 0 - _temp['ps_ship_takeoffs'] = 0 - _temp['ps_ship_landings'] = 0 - _temp['ps_farp_takeoffs'] = 0 - _temp['ps_farp_landings'] = 0 - _temp['ps_other_takeoffs'] = 0 - _temp['ps_other_landings'] = 0 - - Perun.StatData[_ucid]=_temp - end - - if argType ~= nil then - Perun.StatData[_ucid]['ps_type']=argType; - Perun.StatDataLastType[net.get_player_info(argPlayerID, 'ucid')]=_ucid - else - -- Do nothing - end - - if argAction == "eject" then - Perun.StatData[_ucid]['ps_ejections']=Perun.StatData[_ucid]['ps_ejections']+1 - elseif argAction == "pilot_death" then - if DCS.getModelTime() > 300 then - -- we do not track deaths during first 5 minutes due to spawning issues TBD - Perun.StatData[_ucid]['ps_deaths']=Perun.StatData[_ucid]['ps_deaths']+1 - end - elseif argAction == "friendly_fire" then - Perun.StatData[_ucid]['ps_teamkills']=Perun.StatData[_ucid]['ps_teamkills']+1 - elseif argAction == "crash" then - Perun.StatData[_ucid]['ps_crashes']=Perun.StatData[_ucid]['ps_crashes']+1 - elseif argAction == "landing_FARP" then - Perun.StatData[_ucid]['ps_farp_landings']=Perun.StatData[_ucid]['ps_farp_landings']+1 - elseif argAction == "landing_SHIP" then - Perun.StatData[_ucid]['ps_ship_landings']=Perun.StatData[_ucid]['ps_ship_landings']+1 - elseif argAction == "landing_AIRFIELD" then - Perun.StatData[_ucid]['ps_airfield_landings']=Perun.StatData[_ucid]['ps_airfield_landings']+1 - elseif argAction == "tookoff_FARP" then - Perun.StatData[_ucid]['ps_farp_takeoffs']=Perun.StatData[_ucid]['ps_farp_takeoffs']+1 - elseif argAction == "tookoff_SHIP" then - Perun.StatData[_ucid]['ps_ship_takeoffs']=Perun.StatData[_ucid]['ps_ship_takeoffs']+1 - elseif argAction == "tookoff_AIRFIELD" then - Perun.StatData[_ucid]['ps_airfield_takeoffs']=Perun.StatData[_ucid]['ps_airfield_takeoffs']+1 - elseif argAction == "kill_Planes" then - Perun.StatData[_ucid]['ps_kills_planes']=Perun.StatData[_ucid]['ps_kills_planes']+1 - elseif argAction == "kill_Helicopters" then - Perun.StatData[_ucid]['ps_kills_helicopters']=Perun.StatData[_ucid]['ps_kills_helicopters']+1 - elseif argAction == "kill_Ships" then - Perun.StatData[_ucid]['ps_kills_ships']=Perun.StatData[_ucid]['ps_kills_ships']+1 - elseif argAction == "kill_Air_Defence" then - Perun.StatData[_ucid]['ps_kills_air_defense']=Perun.StatData[_ucid]['ps_kills_air_defense']+1 - elseif argAction == "kill_Unarmed" then - Perun.StatData[_ucid]['ps_kills_unarmed']=Perun.StatData[_ucid]['ps_kills_unarmed']+1 - elseif argAction == "kill_Armor" then - Perun.StatData[_ucid]['ps_kills_armor']=Perun.StatData[_ucid]['ps_kills_armor']+1 - elseif argAction == "kill_Infantry" then - Perun.StatData[_ucid]['ps_kills_infantry']=Perun.StatData[_ucid]['ps_kills_infantry']+1 - elseif argAction == "kill_Fortification" then - Perun.StatData[_ucid]['ps_kills_fortification']=Perun.StatData[_ucid]['ps_kills_fortification']+1 - elseif argAction == "kill_Other" then - Perun.StatData[_ucid]['ps_kills_other']=Perun.StatData[_ucid]['ps_kills_other']+1 - elseif argAction == "kill_PvP" then - Perun.StatData[_ucid]['ps_pvp']=Perun.StatData[_ucid]['ps_pvp']+1 - elseif argAction == "landing_OTHER" then - Perun.StatData[_ucid]['ps_other_landings']=Perun.StatData[_ucid]['ps_other_landings']+1 - elseif argAction == "tookoff_OTHER" then - Perun.StatData[_ucid]['ps_other_takeoffs']=Perun.StatData[_ucid]['ps_other_takeoffs']+1 - end - - Perun.LogStats(argPlayerID); -end - -Perun.LogStatsGet = function(playerID) - -- Gets Perun statistics array per player - - local next = next - if next(Perun.StatDataLastType) == nil then - _ucid=net.get_player_info(playerID, 'ucid')..DCS.getUnitType(playerID) - elseif Perun.StatDataLastType[net.get_player_info(playerID, 'ucid')]== nil then - _ucid=net.get_player_info(playerID, 'ucid')..DCS.getUnitType(playerID) - else - _ucid=Perun.StatDataLastType[net.get_player_info(playerID, 'ucid')] - end - - local next = next - if next(Perun.StatData) == nil then - Perun.LogStatsCount(playerID,'init') - - end - if Perun.StatData[_ucid]== nil then - Perun.LogStatsCount(playerID,'init') - end - return Perun.StatData[_ucid]; -end - - - -Perun.LogStats = function(playerID) - -- Log player status - - -- TBD : DCS stats not working at the moment WIP - p_stats_dcs={} - p_stats_dcs['PS_CAR']=net.get_stat(playerID,2) - p_stats_dcs['PS_PLANE']=net.get_stat(playerID,3) - p_stats_dcs['PS_SHIP']=net.get_stat(playerID,4) - p_stats_dcs['PS_SCORE']=net.get_stat(playerID,5) - p_stats_dcs['PS_LAND']=net.get_stat(playerID,6) - p_stats_dcs['PS_PING']=net.get_stat(playerID,0) - p_stats_dcs['PS_CRASH']=net.get_stat(playerID,1) - p_stats_dcs['PS_EJECT']=net.get_stat(playerID,7) - - _temp=Perun.LogStatsGet(playerID) - data={} - data['stat_data_dcs']=p_stats_dcs - data['stat_data_perun']=_temp - data['stat_data_type']=_temp['ps_type']; - data['stat_ucid']=net.get_player_info(playerID, 'ucid') - data['stat_name']=net.get_player_info(playerID, 'name') - data['stat_datetime']=os.date('%Y-%m-%d %H:%M:%S') - data['stat_missionhash']=Perun.MissionHash - - Perun.SendToPerun(52,data) -end - -Perun.LogLogin = function(playerID) - -- Player logged in - - data={} - data['login_ucid']=net.get_player_info(playerID, 'ucid') - data['login_ipaddr']=net.get_player_info(playerID, 'ipaddr') - data['login_name']=net.get_player_info(playerID, 'name') - data['login_datetime']=os.date('%Y-%m-%d %H:%M:%S') - - Perun.SendToPerun(53,data) -end - -Perun.CheckMulticrew = function (owner_playerID,owner_unittype) - -- Check multicrew and return list of co-player - - _coplayers = {} - table.insert(_coplayers, owner_playerID) - if owner_unittype == "F-14B" or owner_unittype == "Yak-52" or owner_unittype == "L-39C" or owner_unittype == "SA342M" or owner_unittype =="SA342Minigun" or owner_unittype == "SA342Mistral" or owner_unittype == "SA342L" then -- TBD add additional multicrew model types - - _owner_slot=net.get_player_info(owner_playerID, 'slot') - _owner_side=net.get_player_info(owner_playerID, 'side') - - -- Check if we are co-player - _t_start, _t_end = string.find(_owner_slot, '_%d+') - _sub_slot = nil - if _t_start then - -- This is co-player - _master_slot = string.sub(_owner_slot, 0 , _t_start -1 ) - _sub_slot = string.sub(_owner_slot, _t_start + 1, _t_end ) - else - _master_slot = _owner_slot - - end - - if _master_slot ~= "" then - -- Search for all players to account for event - _all_players = net.get_player_list() - for PlayerIDIndex, playerID in ipairs(_all_players) do - local _playerDetails = net.get_player_info( playerID ) - - if _playerDetails.side == _owner_side and (_playerDetails.slot == _master_slot or _playerDetails.slot == _master_slot .. "_1" or _playerDetails.slot == _master_slot .. "_2") and playerID ~= owner_playerID then - -- Let's build coplayers list - table.insert(_coplayers, playerID) - else - -- No coplayers - end - end - end - end - return _coplayers -end - - ---- ########### Event callbacks ########### +--- ################################ Event callbacks ################################ Perun.onSimulationStart = function() - Perun.MissionHash=DCS.getMissionName( ).."@".. Perun.Instance .. "@"..os.date('%Y%m%d_%H%M%S'); + -- Simulation was started + Perun.MissionHash=Perun.GenerateMissionHash() Perun.LogEvent("SimStart","Mission " .. Perun.MissionHash .. " started",nil,nil); Perun.StatData = {} Perun.StatDataLastType = {} + -- TBD send this to Perun end Perun.onSimulationStop = function() + -- Simulation was stopped Perun.LogEvent("SimStop","Mission " .. Perun.MissionHash .. " finished",nil,nil); - Perun.MissionHash=""; + Perun.MissionHash=Perun.GenerateMissionHash(); Perun.StatData = {} Perun.StatDataLastType = {} + -- TBD send this to Perun end Perun.onPlayerDisconnect = function(id, err_code) + -- Player disconnected - TBD DCS Bug, this is not triggered at this point of time Perun.LogEvent("disconnect", "Player " .. net.get_player_info(id, "name") .. " disconnected; " .. err_code,net.get_player_info(id, "name"),err_code); + -- TBD send this to Perun end Perun.onSimulationFrame = function() + -- Repeat for each simulator frame local _now = DCS.getRealTime() -- First run @@ -568,19 +693,21 @@ Perun.onSimulationFrame = function() end -- Send keepalive - if _now > Perun.lastSentKeepAlive + 3 then + if _now > Perun.lastSentKeepAlive + Perun.SendRetries then Perun.lastSentKeepAlive = _now Perun.SendToPerun(0,nil) end end Perun.onPlayerStart = function (id) + -- Player entered cocpit net.send_chat_to(Perun.MOTD_L1, id); net.send_chat_to(Perun.MOTD_L2, id); end Perun.onPlayerTrySendChat = function (playerID, msg, all) - if msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 then + -- Somebody tries to send chat message + if msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 and msg~=Perun.ConnectionError then Perun.LogChat(playerID,msg,all) end @@ -588,13 +715,15 @@ Perun.onPlayerTrySendChat = function (playerID, msg, all) end Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7) + -- Game event has occured + Perun.AddLog("Event handler for ".. eventName .. " started",2) if eventName == "friendly_fire" then --"friendly_fire", playerID, weaponName, victimPlayerID if arg2 == "" then arg2 = "Cannon" end - Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name").." killed friendy " .. net.get_player_info(arg3, "name") .. " using " .. arg2,nil,nil); + Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .." killed friendly " .. Perun.GetMulticrewCrewNames(arg3) .. " using " .. arg2,nil,nil); elseif eventName == "mission_end" then --"mission_end", winner, msg @@ -602,79 +731,77 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7) elseif eventName == "kill" then --"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName - _tempX="" + local _temp_victims="" if net.get_player_info(arg4, "name") ~= nil then - _tempX = " player ".. net.get_player_info(arg4, "name") .." "; + _temp_victims = " player(s) ".. Perun.GetMulticrewCrewNames(arg4) .. " "; Perun.LogStats(arg4); else - _tempX = " AI "; + _temp_victims = " AI "; end if net.get_player_info(arg1, "name") ~= nil then - _temp2 = " player ".. net.get_player_info(arg1, "name") .." "; + _temp_killers = " player(s) ".. Perun.GetMulticrewCrewNames(arg1) .." "; _temp_event_type="" if arg3 ~= arg6 then - if Perun.GetCategory(arg5) == "Planes" then + _temp_category=Perun.GetCategory(arg5) + + if _temp_category == "Planes" then _temp_event_type="kill_Planes" - elseif Perun.GetCategory(arg5) == "Helicopters" then + elseif _temp_category == "Helicopters" then _temp_event_type="kill_Helicopters" - elseif Perun.GetCategory(arg5) == "Ships" then + elseif _temp_category == "Ships" then _temp_event_type="kill_Ships" - elseif Perun.GetCategory(arg5) == "Air Defence" then + elseif _temp_category == "Air Defence" then _temp_event_type="kill_Air_Defence" - elseif Perun.GetCategory(arg5) == "Unarmed" then + elseif _temp_category == "Unarmed" then _temp_event_type="kill_Unarmed" - elseif Perun.GetCategory(arg5) == "Armor" then + elseif _temp_category == "Armor" then _temp_event_type="kill_Armor" - elseif Perun.GetCategory(arg5) == "Infantry" then + elseif _temp_category == "Infantry" then _temp_event_type="kill_Infantry" - elseif Perun.GetCategory(arg5) == "Fortification" then + elseif _temp_category == "Fortification" then _temp_event_type="kill_Fortification" else _temp_event_type="kill_Other" end if net.get_player_info(arg4, "name") ~= nil and arg3 ~= arg6 then - pilots_accounted = Perun.CheckMulticrew(arg1,arg2) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,"kill_PvP",DCS.getUnitType(arg2)); - end + Perun.LogStatsCountCrew (arg1,"kill_PvP") end else _temp_event_type="friendly_fire" end - pilots_accounted = Perun.CheckMulticrew(arg1,arg2) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,_temp_event_type,DCS.getUnitType(arg2)); - end + Perun.LogStatsCountCrew (arg1,_temp_event_type) else - _temp2 = " AI "; + _temp_killers = " AI "; end if arg7 == "" then arg7 = "Cannon" end - Perun.LogEvent(eventName,Perun.SideID2Name(arg3) .. _temp2 .. " in " .. arg2 .. " killed " .. Perun.SideID2Name(arg6) .. _tempX .. " in " .. arg5 .. " using " .. arg7 .. " [".. Perun.GetCategory(arg5).."]",arg7,Perun.GetCategory(arg5)); + Perun.LogEvent(eventName,Perun.SideID2Name(arg3) .. _temp_killers .. " in " .. arg2 .. " killed " .. Perun.SideID2Name(arg6) .. _temp_victims .. " in " .. arg5 .. " using " .. arg7 .. " [".. Perun.GetCategory(arg5).."]",arg7,Perun.GetCategory(arg5)); + elseif eventName == "self_kill" then --"self_kill", playerID + Perun.LogStats(arg1); Perun.LogEvent(eventName,net.get_player_info(arg1, "name") .. " killed himself",nil,nil); - Perun.LogStats(arg1); elseif eventName == "change_slot" then --"change_slot", playerID, slotID, prevSide - if DCS.getUnitType(arg2) ~= nil and DCS.getUnitType(arg2) ~= "" then - _temp = DCS.getUnitType(arg2); - else - _temp = "SPECTATOR"; - end - - Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " changed slot to " .. _temp,nil,nil); - Perun.LogStatsCount(arg1,"init",_temp) + Perun.LogStatsCount(arg1,"init") + _master_type,_master_slot,_sub_slot = Perun.GetMulticrewAllParameters(arg1) + if _sub_slot == nil then + _sub_slot ="" + else + _sub_slot =" (" .. _sub_slot .. ") " + end + Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " changed slot to " .. _master_type .. " " .. _sub_slot,nil,nil); + elseif eventName == "connect" then --"connect", playerID, name @@ -683,96 +810,82 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7) elseif eventName == "disconnect" then --"disconnect", playerID, name, playerSide, reason_code + Perun.LogStats(arg1); Perun.LogEvent(eventName, Perun.SideID2Name(arg3) .. " player " ..net.get_player_info(arg1, "name") .. " disconnected",nil,nil); - Perun.LogStats(arg1); elseif eventName == "crash" then --"crash", playerID, unit_missionID - Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " crashed in " .. DCS.getUnitType(arg2),nil,nil); - - pilots_accounted = Perun.CheckMulticrew(arg1,DCS.getUnitType(arg2)) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,"crash",DCS.getUnitType(arg2)); - end + Perun.LogStatsCountCrew (arg1,"crash") + Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " crashed in " .. DCS.getUnitType(arg2),nil,nil); + elseif eventName == "eject" then --"eject", playerID, unit_missionID - Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " ejected " .. DCS.getUnitType(arg2),nil,nil); - - pilots_accounted = Perun.CheckMulticrew(arg1,DCS.getUnitType(arg2)) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,"eject",DCS.getUnitType(arg2)); - end - + Perun.LogStatsCountCrew (arg1,"eject") -- TBD crew or initiator only? + Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " ejected " .. DCS.getUnitType(arg2),nil,nil); + elseif eventName == "takeoff" then --"takeoff", playerID, unit_missionID, airdromeName if arg3 ~= "" then - _temp = " from " .. arg3; + _temp_airfield = " from " .. arg3; else - _temp = ""; + _temp_airfield = ""; end - Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " took off in ".. DCS.getUnitType(arg2) .. _temp,arg3,nil); - - _type = "" + -- TBD below shall be moved to function and arrays of strings - see landings + _temp_type = "" if string.find(arg3, "FARP",1,true) then - _type="tookoff_FARP" - elseif string.find(arg3, "CVN-74 John C. Stennis",1,true) or string.find(arg3, "LHA-1 Tarawa",1,true) then - _type="tookoff_SHIP" + _temp_type="tookoff_FARP" + elseif string.find(arg3, "CVN-74 John C. Stennis",1,true) or string.find(arg3, "LHA-1 Tarawa",1,true) or string.find(arg3, "SHIP",1,true) then + _temp_type="tookoff_SHIP" elseif arg3 ~= "" then - _type="tookoff_AIRFIELD" + _temp_type="tookoff_AIRFIELD" else - _type="tookoff_OTHER" - end - - pilots_accounted = Perun.CheckMulticrew(arg1,DCS.getUnitType(arg2)) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,_type,DCS.getUnitType(arg2)) + _temp_type="tookoff_OTHER" end + Perun.LogStatsCountCrew (arg1,_temp_type) + Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " took off in ".. DCS.getUnitType(arg2) .. _temp_airfield,arg3,nil); + elseif eventName == "landing" then --"landing", playerID, unit_missionID, airdromeName if arg3 ~= "" then - _temp = " at " .. arg3; + _temp_airfield = " at " .. arg3; else - _temp =""; + _temp_airfield =""; end - Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " landed in " .. DCS.getUnitType(arg2).. _temp,arg3,nil); - - _type = "" + -- TBD below shall be moved to function and arrays of strings - see takeoffs + _temp_type = "" if string.find(arg3, "FARP",1,true) then - _type = "landing_FARP" - elseif string.find(arg3, "CVN-74 John C. Stennis",1,true) or string.find(arg3, "LHA-1 Tarawa",1,true) then - _type = "landing_SHIP" + _temp_type = "landing_FARP" + elseif string.find(arg3, "CVN-74 John C. Stennis",1,true) or string.find(arg3, "LHA-1 Tarawa",1,true) or string.find(arg3, "SHIP",1,true) then + _temp_type = "landing_SHIP" elseif arg3 ~= "" then - _type = "landing_AIRFIELD" + _temp_type = "landing_AIRFIELD" else - _type = "landing_OTHER" - end - - pilots_accounted = Perun.CheckMulticrew(arg1,DCS.getUnitType(arg2)) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,_type,DCS.getUnitType(arg2)) + _temp_type = "landing_OTHER" end + Perun.LogStatsCountCrew (arg1,_temp_type) + Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " landed in " .. DCS.getUnitType(arg2).. _temp_airfield,arg3,nil); + elseif eventName == "pilot_death" then --"pilot_death", playerID, unit_missionID - Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " in " .. DCS.getUnitType(arg2) .. " died",nil,nil); + Perun.LogStatsCountCrew (arg1,"pilot_death") -- TBD crew or initiator only? + Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " in " .. DCS.getUnitType(arg2) .. " died",nil,nil); - pilots_accounted = Perun.CheckMulticrew(arg1,DCS.getUnitType(arg2)) - for _, pilotID in ipairs(pilots_accounted) do - Perun.LogStatsCount(pilotID,"pilot_death",DCS.getUnitType(arg2)) - end - else Perun.LogEvent(eventName,"Unknown event type",nil,nil); - end + end + Perun.AddLog("Event handler for " .. eventName .. " finished",2) end -- ########### Finalize and set callbacks ########### if Perun.IsServer then - DCS.setUserCallbacks(Perun) - net.log("Perun by VladMordock was loaded: " .. Perun.Version ) - Perun.ConnectToPerun() + -- If this game instance is hosting multiplayer game, start Perun + Perun.MissionHash=Perun.GenerateMissionHash() -- Generate initial missionhash + DCS.setUserCallbacks(Perun) -- Set user callbacs, map DCS event handlers with functions defined above + net.log("Perun by VladMordock was loaded: " .. Perun.Version ) -- Display perun information in log + Perun.ConnectToPerun() -- Connect to Perun server end diff --git a/02_Windows_App/Perun_v1/01_Classes/DatabaseController.cs b/02_Windows_App/Perun_v1/01_Classes/DatabaseController.cs index e4e441c..6887e80 100644 --- a/02_Windows_App/Perun_v1/01_Classes/DatabaseController.cs +++ b/02_Windows_App/Perun_v1/01_Classes/DatabaseController.cs @@ -21,7 +21,6 @@ public class DatabaseController string TCPFrameTimestamp = TCPFrame.timestamp; string TCPFrameInstance = TCPFrame.instance; string TCPFramePayload; - string TCPFramePayload_Perun; string SQLQueryTxt; int ReturnValue = 1; @@ -64,16 +63,13 @@ public class DatabaseController else if (TCPFrameType == "52") { // Update user stats - TCPFramePayload = JsonConvert.SerializeObject(TCPFrame.payload.stat_data_dcs); // Deserialize payload - TCPFramePayload_Perun = JsonConvert.SerializeObject(TCPFrame.payload.stat_data_perun); // Deserialize payload - SQLQueryTxt = "INSERT INTO `pe_DataMissionHashes` (`pe_DataMissionHashes_hash`,`pe_DataMissionHashes_instance`) SELECT '" + TCPFrame.payload.stat_missionhash + "','" + TCPFrameInstance + "' FROM DUAL WHERE NOT EXISTS (SELECT * FROM `pe_DataMissionHashes` where `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "' AND `pe_DataMissionHashes_instance`=" + TCPFrameInstance + ");"; SQLQueryTxt += "UPDATE `pe_DataMissionHashes` SET `pe_DataMissionHashes_datetime` = " + TCPFrameTimestamp + " WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "' AND `pe_DataMissionHashes_instance`=" + TCPFrameInstance + " ;"; SQLQueryTxt += "INSERT INTO `pe_DataPlayers` (`pe_DataPlayers_ucid`) SELECT '" + TCPFrame.payload.stat_ucid + "' FROM DUAL WHERE NOT EXISTS (SELECT * FROM `pe_DataPlayers` where `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "');"; SQLQueryTxt += "UPDATE `pe_DataPlayers` SET `pe_DataPlayers_updated`=" + TCPFrameTimestamp + " WHERE `pe_DataPlayers_ucid`='" + TCPFrame.payload.stat_ucid + "';"; SQLQueryTxt += "INSERT INTO `pe_DataTypes` (`pe_DataTypes_name`) SELECT '" + TCPFrame.payload.stat_data_type + "' FROM DUAL WHERE NOT EXISTS (SELECT * FROM `pe_DataTypes` where `pe_DataTypes_name` = '" + TCPFrame.payload.stat_data_type + "');"; SQLQueryTxt += "INSERT INTO `pe_LogStats` (`pe_LogStats_playerid`,`pe_LogStats_missionhash_id`,`pe_LogStats_typeid`) SELECT (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "'), (SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "'), (SELECT pe_DataTypes_id FROM `pe_DataTypes` where `pe_DataTypes_name` = '" + TCPFrame.payload.stat_data_type + "') FROM DUAL WHERE NOT EXISTS (SELECT * FROM `pe_LogStats` WHERE `pe_LogStats_missionhash_id`=(SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "') AND `pe_LogStats_playerid` = (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "') AND `pe_LogStats_typeid`= (SELECT pe_DataTypes_id FROM `pe_DataTypes` where `pe_DataTypes_name` = '" + TCPFrame.payload.stat_data_type + "'));"; - SQLQueryTxt += "UPDATE `pe_LogStats` SET `ps_kills_fortification`=" + TCPFrame.payload.stat_data_perun.ps_kills_fortification + ",`ps_other_landings`=" + TCPFrame.payload.stat_data_perun.ps_other_landings + ",`ps_other_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_other_takeoffs + ",`ps_pvp`=" + TCPFrame.payload.stat_data_perun.ps_pvp + ",`ps_deaths`=" + TCPFrame.payload.stat_data_perun.ps_deaths + ",`ps_ejections`=" + TCPFrame.payload.stat_data_perun.ps_ejections + ",`ps_crashes`=" + TCPFrame.payload.stat_data_perun.ps_crashes + ",`ps_teamkills`=" + TCPFrame.payload.stat_data_perun.ps_teamkills + ",`ps_kills_planes`=" + TCPFrame.payload.stat_data_perun.ps_kills_planes + ",`ps_kills_helicopters`=" + TCPFrame.payload.stat_data_perun.ps_kills_helicopters + ",`ps_kills_air_defense`=" + TCPFrame.payload.stat_data_perun.ps_kills_air_defense + ",`ps_kills_armor`=" + TCPFrame.payload.stat_data_perun.ps_kills_armor + ",`ps_kills_unarmed`=" + TCPFrame.payload.stat_data_perun.ps_kills_unarmed + ",`ps_kills_infantry`=" + TCPFrame.payload.stat_data_perun.ps_kills_infantry + ",`ps_kills_ships`=" + TCPFrame.payload.stat_data_perun.ps_kills_ships + ",`ps_kills_other`=" + TCPFrame.payload.stat_data_perun.ps_kills_other + ",`ps_airfield_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_airfield_takeoffs + ",`ps_airfield_landings`=" + TCPFrame.payload.stat_data_perun.ps_airfield_landings + ",`ps_ship_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_ship_takeoffs + ",`ps_ship_landings`=" + TCPFrame.payload.stat_data_perun.ps_ship_landings + ",`ps_farp_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_farp_takeoffs + ",`ps_farp_landings`=" + TCPFrame.payload.stat_data_perun.ps_farp_landings + ", `pe_LogStats_datetime`='" + TCPFrame.payload.stat_datetime + "',`pe_LogStats_playerid` = (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "'),`pe_LogStats_missionhash_id`=(SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "') WHERE `pe_LogStats_missionhash_id`=(SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "') AND `pe_LogStats_playerid` = (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "') AND `pe_LogStats_typeid`=(SELECT pe_DataTypes_id FROM `pe_DataTypes` where `pe_DataTypes_name` = '" + TCPFrame.payload.stat_data_type + "');"; + SQLQueryTxt += "UPDATE `pe_LogStats` SET `pe_LogStats_seat`=" + TCPFrame.payload.stat_data_subslot + ",`pe_LogStats_masterslot`=" + TCPFrame.payload.stat_data_masterslot + ",`ps_kills_fortification`=" + TCPFrame.payload.stat_data_perun.ps_kills_fortification + ",`ps_other_landings`=" + TCPFrame.payload.stat_data_perun.ps_other_landings + ",`ps_other_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_other_takeoffs + ",`ps_pvp`=" + TCPFrame.payload.stat_data_perun.ps_pvp + ",`ps_deaths`=" + TCPFrame.payload.stat_data_perun.ps_deaths + ",`ps_ejections`=" + TCPFrame.payload.stat_data_perun.ps_ejections + ",`ps_crashes`=" + TCPFrame.payload.stat_data_perun.ps_crashes + ",`ps_teamkills`=" + TCPFrame.payload.stat_data_perun.ps_teamkills + ",`ps_kills_planes`=" + TCPFrame.payload.stat_data_perun.ps_kills_planes + ",`ps_kills_helicopters`=" + TCPFrame.payload.stat_data_perun.ps_kills_helicopters + ",`ps_kills_air_defense`=" + TCPFrame.payload.stat_data_perun.ps_kills_air_defense + ",`ps_kills_armor`=" + TCPFrame.payload.stat_data_perun.ps_kills_armor + ",`ps_kills_unarmed`=" + TCPFrame.payload.stat_data_perun.ps_kills_unarmed + ",`ps_kills_infantry`=" + TCPFrame.payload.stat_data_perun.ps_kills_infantry + ",`ps_kills_ships`=" + TCPFrame.payload.stat_data_perun.ps_kills_ships + ",`ps_kills_other`=" + TCPFrame.payload.stat_data_perun.ps_kills_other + ",`ps_airfield_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_airfield_takeoffs + ",`ps_airfield_landings`=" + TCPFrame.payload.stat_data_perun.ps_airfield_landings + ",`ps_ship_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_ship_takeoffs + ",`ps_ship_landings`=" + TCPFrame.payload.stat_data_perun.ps_ship_landings + ",`ps_farp_takeoffs`=" + TCPFrame.payload.stat_data_perun.ps_farp_takeoffs + ",`ps_farp_landings`=" + TCPFrame.payload.stat_data_perun.ps_farp_landings + ", `pe_LogStats_datetime`='" + TCPFrame.payload.stat_datetime + "',`pe_LogStats_playerid` = (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "'),`pe_LogStats_missionhash_id`=(SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "') WHERE `pe_LogStats_missionhash_id`=(SELECT `pe_DataMissionHashes_id` FROM `pe_DataMissionHashes` WHERE `pe_DataMissionHashes_hash` = '" + TCPFrame.payload.stat_missionhash + "') AND `pe_LogStats_playerid` = (SELECT `pe_DataPlayers_id` from `pe_DataPlayers` WHERE `pe_DataPlayers_ucid` = '" + TCPFrame.payload.stat_ucid + "') AND `pe_LogStats_typeid`=(SELECT pe_DataTypes_id FROM `pe_DataTypes` where `pe_DataTypes_name` = '" + TCPFrame.payload.stat_data_type + "');"; } else if (TCPFrameType == "53") { @@ -180,19 +176,30 @@ public class DatabaseController { case 1042: // Unable to connect to any of the specified MySQL hosts (Check Server,Port) PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - unable to connect, error: " + m_ex.Message,1,1, TCPFrameType); + DatabaseStatus = false; + ReturnValue = 0; break; case 0: // Access denied (Check DB name,username,password) PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - access denied, error: " + m_ex.Message,1,1, TCPFrameType); + DatabaseStatus = false; + ReturnValue = 0; + break; + case 1064: // Incorrect query + PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - query: " + SQLQueryTxt, 1, 1, TCPFrameType); + PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - error: " + m_ex.Message, 1, 1, TCPFrameType); + DatabaseStatus = true; // True as connection is not broken + ReturnValue = 1; // Return a value to remove this query from quae break; default: PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - error id: " + m_ex.Number,1,1, TCPFrameType); PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - query: " + SQLQueryTxt, 1, 1, TCPFrameType); PerunHelper.AddLog(ref Globals.AppLogHistory, "ERROR MySQL - error: " + m_ex.Message,1,1, TCPFrameType); + DatabaseStatus = false; + ReturnValue = 0; break; } Globals.ErrorsGame++; - DatabaseStatus = false; - ReturnValue = 0; + } DatabaseConnection.Close(); Console.WriteLine("Sending data to MySQL - Done"); diff --git a/02_Windows_App/Perun_v1/Perun_v1.csproj b/02_Windows_App/Perun_v1/Perun_v1.csproj index 2cedcb0..76a74fd 100644 --- a/02_Windows_App/Perun_v1/Perun_v1.csproj +++ b/02_Windows_App/Perun_v1/Perun_v1.csproj @@ -34,7 +34,7 @@ true Perun.htm 0 - 0.8.3.%2a + 0.9.0.%2a false true true diff --git a/02_Windows_App/Perun_v1/Properties/AssemblyInfo.cs b/02_Windows_App/Perun_v1/Properties/AssemblyInfo.cs index c0d428c..92ae68d 100644 --- a/02_Windows_App/Perun_v1/Properties/AssemblyInfo.cs +++ b/02_Windows_App/Perun_v1/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ using System.Runtime.InteropServices; // Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki // przy użyciu symbolu „*”, tak jak pokazano poniżej: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.3.0")] -[assembly: AssemblyFileVersion("0.8.3.0")] +[assembly: AssemblyVersion("0.9.0.0")] +[assembly: AssemblyFileVersion("0.9.0.0")] [assembly: NeutralResourcesLanguage("en")] diff --git a/03_MySQL/m1081_perun.sql b/03_MySQL/m1081_perun.sql index e0b09d5..590ba41 100644 --- a/03_MySQL/m1081_perun.sql +++ b/03_MySQL/m1081_perun.sql @@ -11,33 +11,6 @@ SET time_zone = "+00:00"; CREATE DATABASE IF NOT EXISTS `m1081_perun` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE `m1081_perun`; -DELIMITER $$ -DROP PROCEDURE IF EXISTS `sp_CleanDatabase`$$ -CREATE DEFINER=`m1081`@`%.devil` PROCEDURE `sp_CleanDatabase` () MODIFIES SQL DATA -BEGIN - DELETE FROM pe_DataMissionHashes; - DELETE FROM pe_DataPlayers; - DELETE FROM pe_DataRaw; - DELETE FROM pe_LogChat; - DELETE FROM pe_LogEvent; - DELETE FROM pe_LogLogins; - DELETE FROM pe_LogStats; - - ALTER TABLE pe_DataMissionHashes AUTO_INCREMENT = 1; - ALTER TABLE pe_DataPlayers AUTO_INCREMENT = 1; - ALTER TABLE pe_DataRaw AUTO_INCREMENT = 1; - ALTER TABLE pe_LogChat AUTO_INCREMENT = 1; - ALTER TABLE pe_LogEvent AUTO_INCREMENT = 1; - ALTER TABLE pe_LogLogins AUTO_INCREMENT = 1; - ALTER TABLE pe_LogStats AUTO_INCREMENT = 1; - -END$$ - -DROP PROCEDURE IF EXISTS `sp_SyncForumGame`$$ -$$ - -DELIMITER ; - DROP TABLE IF EXISTS `pe_Config`; CREATE TABLE IF NOT EXISTS `pe_Config` ( `pe_Config_id` int(11) NOT NULL, @@ -46,7 +19,7 @@ CREATE TABLE IF NOT EXISTS `pe_Config` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES -(1, 'v0.8.3'); +(1, 'v0.9.0'); DROP TABLE IF EXISTS `pe_DataMissionHashes`; CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` ( @@ -57,7 +30,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 AUTO_INCREMENT=818 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=7215 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_DataPlayers`; CREATE TABLE IF NOT EXISTS `pe_DataPlayers` ( @@ -68,7 +41,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 AUTO_INCREMENT=219 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=226 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_DataRaw`; CREATE TABLE IF NOT EXISTS `pe_DataRaw` ( @@ -76,7 +49,9 @@ CREATE TABLE IF NOT EXISTS `pe_DataRaw` ( `pe_dataraw_instance` smallint(5) UNSIGNED NOT NULL, `pe_dataraw_payload` text NOT NULL, `pe_dataraw_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`) + 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; DROP TABLE IF EXISTS `pe_DataTypes`; @@ -86,7 +61,7 @@ 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 AUTO_INCREMENT=57 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_LogChat`; CREATE TABLE IF NOT EXISTS `pe_LogChat` ( @@ -99,8 +74,9 @@ CREATE TABLE IF NOT EXISTS `pe_LogChat` ( PRIMARY KEY (`pe_LogChat_id`), KEY `pe_LogChat_missionhash_id` (`pe_LogChat_missionhash_id`), KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`), - KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`) -) ENGINE=InnoDB AUTO_INCREMENT=19739 DEFAULT CHARSET=utf8; + KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`), + KEY `pe_LogChat_all` (`pe_LogChat_all`) +) ENGINE=InnoDB AUTO_INCREMENT=22686 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_LogEvent`; CREATE TABLE IF NOT EXISTS `pe_LogEvent` ( @@ -111,10 +87,12 @@ CREATE TABLE IF NOT EXISTS `pe_LogEvent` ( `pe_LogEvent_content` text NOT NULL, `pe_LogEvent_arg1` varchar(150) DEFAULT NULL, `pe_LogEvent_arg2` varchar(150) DEFAULT NULL, + `pe_LogEvent_argPlayers` varchar(150) DEFAULT NULL, PRIMARY KEY (`pe_LogEvent_id`), KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`), - KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`) -) ENGINE=InnoDB AUTO_INCREMENT=93657 DEFAULT CHARSET=utf8; + KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`), + KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`) +) ENGINE=InnoDB AUTO_INCREMENT=99807 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_LogLogins`; CREATE TABLE IF NOT EXISTS `pe_LogLogins` ( @@ -126,8 +104,9 @@ CREATE TABLE IF NOT EXISTS `pe_LogLogins` ( `pe_LogLogins_ip` varchar(100) NOT NULL, PRIMARY KEY (`pe_LogLogins_id`), KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`), - KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`) -) ENGINE=InnoDB AUTO_INCREMENT=10742 DEFAULT CHARSET=utf8; + KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`), + KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`) +) ENGINE=InnoDB AUTO_INCREMENT=11344 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pe_LogStats`; CREATE TABLE IF NOT EXISTS `pe_LogStats` ( @@ -136,6 +115,8 @@ CREATE TABLE IF NOT EXISTS `pe_LogStats` ( `pe_LogStats_missionhash_id` bigint(20) DEFAULT NULL, `pe_LogStats_playerid` bigint(20) DEFAULT NULL, `pe_LogStats_typeid` int(11) DEFAULT NULL, + `pe_LogStats_masterslot` int(11) DEFAULT NULL, + `pe_LogStats_seat` int(10) UNSIGNED DEFAULT NULL, `ps_kills` int(11) UNSIGNED NOT NULL DEFAULT '0', `ps_pvp` int(11) UNSIGNED NOT NULL DEFAULT '0', `ps_deaths` int(11) UNSIGNED NOT NULL DEFAULT '0', @@ -160,40 +141,29 @@ CREATE TABLE IF NOT EXISTS `pe_LogStats` ( `ps_other_landings` int(10) UNSIGNED NOT NULL DEFAULT '0', `ps_other_takeoffs` int(10) UNSIGNED NOT NULL DEFAULT '0', `pe_LogStats_mstatus` enum('?','RTB','MIA','KIA') DEFAULT NULL, - `ps_takeoffs` int(10) UNSIGNED DEFAULT '0', - `ps_landings` int(10) UNSIGNED DEFAULT '0', - `ps_kills_AA` int(10) UNSIGNED DEFAULT '0', - `ps_kills_AG` int(10) UNSIGNED DEFAULT '0', - `ps_kills_AS` int(10) UNSIGNED DEFAULT '0', - `ps_kills_AAA` int(10) UNSIGNED DEFAULT '0', PRIMARY KEY (`pe_LogStats_id`), UNIQUE KEY `UNIQUE_STATS_PER_MISSION_AND_UCID_AND_TYPE` (`pe_LogStats_playerid`,`pe_LogStats_missionhash_id`,`pe_LogStats_typeid`) USING BTREE, KEY `pe_LogStats_missionhash_id` (`pe_LogStats_missionhash_id`), KEY `pe_LogStats_playerid` (`pe_LogStats_playerid`), - KEY `pe_LogStats_typeid` (`pe_LogStats_typeid`) -) ENGINE=InnoDB AUTO_INCREMENT=9595 DEFAULT CHARSET=utf8; + KEY `pe_LogStats_typeid` (`pe_LogStats_typeid`), + KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`), + KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`), + KEY `pe_LogStats_seat` (`pe_LogStats_seat`) +) ENGINE=InnoDB AUTO_INCREMENT=10220 DEFAULT CHARSET=utf8; DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`; DELIMITER $$ CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN - - SET NEW.ps_takeoffs = NEW.ps_airfield_takeoffs + NEW.ps_farp_takeoffs + NEW.ps_other_takeoffs + NEW.ps_ship_takeoffs; - SET NEW.ps_landings = NEW.ps_airfield_landings + NEW.ps_farp_landings + NEW.ps_other_landings + NEW.ps_ship_landings; - SET NEW.ps_kills_AA = NEW.ps_kills_planes + NEW.ps_kills_helicopters; - SET NEW.ps_kills_AG = NEW.ps_kills_armor + NEW.ps_kills_unarmed + NEW.ps_kills_fortification; - SET NEW.ps_kills_AS = NEW.ps_kills_ships; - SET NEW.ps_kills_AAA = NEW.ps_kills_air_defense; - IF NEW.ps_ejections > 0 THEN SET NEW.pe_LogStats_mstatus = "MIA"; ELSEIF NEW.ps_deaths > 0 THEN SET NEW.pe_LogStats_mstatus = "KIA"; - ELSEIF NEW.ps_landings >= NEW.ps_takeoffs THEN - IF NEW.ps_takeoffs > 0 or NEW.ps_landings > 0 THEN + ELSEIF (NEW.ps_airfield_landings + NEW.ps_farp_landings + NEW.ps_other_landings + NEW.ps_ship_landings) >= (NEW.ps_airfield_takeoffs + NEW.ps_farp_takeoffs + NEW.ps_other_takeoffs + NEW.ps_ship_takeoffs) THEN + IF (NEW.ps_airfield_takeoffs + NEW.ps_farp_takeoffs + NEW.ps_other_takeoffs + NEW.ps_ship_takeoffs) > 0 or (NEW.ps_airfield_landings + NEW.ps_farp_landings + NEW.ps_other_landings + NEW.ps_ship_landings) > 0 THEN SET NEW.pe_LogStats_mstatus = "RTB"; ELSE SET NEW.pe_LogStats_mstatus = "?"; END IF; - ELSEIF NEW.ps_takeoffs > NEW.ps_landings and NEW.ps_ejections=0 and NEW.ps_deaths=0 THEN + ELSEIF (NEW.ps_airfield_takeoffs + NEW.ps_farp_takeoffs + NEW.ps_other_takeoffs + NEW.ps_ship_takeoffs) > (NEW.ps_airfield_landings + NEW.ps_farp_landings + NEW.ps_other_landings + NEW.ps_ship_landings) and NEW.ps_ejections=0 and NEW.ps_deaths=0 THEN SET NEW.pe_LogStats_mstatus = "MIA"; ELSE SET NEW.pe_LogStats_mstatus = "?"; @@ -203,7 +173,13 @@ $$ DELIMITER ; +ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`); + ALTER TABLE `pe_DataPlayers` ADD FULLTEXT KEY `pe_DataPlayers_ucid` (`pe_DataPlayers_ucid`); + +ALTER TABLE `pe_DataTypes` ADD FULLTEXT KEY `pe_DataTypes_name` (`pe_DataTypes_name`); + +ALTER TABLE `pe_LogChat` ADD FULLTEXT KEY `pe_LogChat_msg` (`pe_LogChat_msg`); COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/README.md b/README.md index b9f47d9..839769b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Provided windows app puts received TCP packets to MySQL database; additionaly Pe However, this software is intended to be used by experienced users - the output is MySQL data and/or JSON file; you will need to process/display it yourself. -![Perun in action](https://i.imgur.com/MieyBej.png) +![Perun in action](https://i.imgur.com/JIlLENa.png) ![Data flow](https://i.imgur.com/JbNu77l.png) ## Prerequisites @@ -55,6 +55,12 @@ Example for windows shortcut: ``` C:\Perun_v1\Perun.exe 48621 1 "G:\DCS SRS\clients-list.json" "C:\Users\DCS\Saved Games\DCS\Mods\tech\LotAtc\stats.json" ``` +## Displaying data +Data displaying and handling is not in the current scope of this project. Since the end of 2018 the Perun data displaying has been hardcoded at forum.gildia.org , website for the Polish DCS community; examples: +![Who is online](https://i.imgur.com/5lVwsJw.png) +![Mission statistics](https://i.imgur.com/uiRSa9e.png) +Due to resource limitations, "Perun for DCS World" will focus on pulling the data from DCS (and external modules) and pushing it to MySQL server, but if anyone would like to develop open sourced PHP applets for data/statistics displaying we're willing to cooperate and support such projects. Unfortunetly it's not possible to share the hardcoded code from forum.gildia.org. + ## Troubleshooting - FAQ - [I keep getting 1305 MySQL error](#i-keep-getting-1305-mysql-error) - [Carrier landing are not tracked correctly](#carrier-landing-are-not-tracked-correctly) @@ -106,7 +112,7 @@ DCS API does not track carrier landing natively, so there is a trick to achive t * [Notepad++](https://notepad-plus-plus.org/) ## Contributing -Please contact me if you'd like to contribute. +Please contact me if you'd like to contribute. Short rule is that all pull request shall come to "dev" branch, pull requests to "master" will be deleted. ## Versioning We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/szporwolik/perun/tags).