mirror of
https://github.com/indig0fox/Arma3-AttendanceTracker.git/
synced 2025-12-08 09:51:47 -06:00
Compare commits
9 Commits
2cbfdcd512
...
v1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
737416965e
|
|||
|
05946c0ebf
|
|||
|
79fb39b154
|
|||
|
fc53ecb770
|
|||
|
dbd3d68537
|
|||
|
3bb8c358fa
|
|||
|
8f971d9887
|
|||
|
f6ff42e467
|
|||
|
7608df9e53
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
\@17thAttendanceTracker/config.json
|
|
||||||
|
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
\@AttendanceTracker/config.json
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
[
|
|
||||||
["OnUserConnected", {
|
|
||||||
params ["_networkId", "_clientStateNumber", "_clientState"];
|
|
||||||
|
|
||||||
[format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
|
|
||||||
private _userInfo = (getUserInfo _networkId);
|
|
||||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
|
||||||
if (_isHC) exitWith {};
|
|
||||||
|
|
||||||
[
|
|
||||||
"ConnectedServer",
|
|
||||||
_playerID,
|
|
||||||
_playerUID,
|
|
||||||
_profileName,
|
|
||||||
_steamName
|
|
||||||
] call attendanceTracker_fnc_logServerEvent;
|
|
||||||
|
|
||||||
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo];
|
|
||||||
}],
|
|
||||||
["OnUserDisconnected", {
|
|
||||||
params ["_networkId", "_clientStateNumber", "_clientState"];
|
|
||||||
|
|
||||||
[format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
|
|
||||||
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId;
|
|
||||||
if (isNil "_userInfo") exitWith {};
|
|
||||||
|
|
||||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
|
||||||
if (_isHC) exitWith {};
|
|
||||||
|
|
||||||
[
|
|
||||||
"DisconnectedServer",
|
|
||||||
_playerID,
|
|
||||||
_playerUID,
|
|
||||||
_profileName,
|
|
||||||
_steamName
|
|
||||||
] call attendanceTracker_fnc_logServerEvent;
|
|
||||||
}],
|
|
||||||
["PlayerConnected", {
|
|
||||||
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
|
||||||
|
|
||||||
[format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
|
|
||||||
private _userInfo = (getUserInfo _idstr);
|
|
||||||
if (isNil "_userInfo") exitWith {};
|
|
||||||
|
|
||||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
|
||||||
|
|
||||||
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo];
|
|
||||||
|
|
||||||
if (_isHC) exitWith {};
|
|
||||||
|
|
||||||
[
|
|
||||||
"ConnectedMission",
|
|
||||||
_playerID,
|
|
||||||
_playerUID,
|
|
||||||
_profileName,
|
|
||||||
_steamName,
|
|
||||||
_jip,
|
|
||||||
roleDescription _unit
|
|
||||||
] call attendanceTracker_fnc_logMissionEvent;
|
|
||||||
}],
|
|
||||||
["PlayerDisconnected", {
|
|
||||||
// NOTE: HandleDisconnect returns a DIFFERENT _id than PlayerDisconnected and above handlers, so we can't use it here
|
|
||||||
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
|
||||||
|
|
||||||
[format ["(EventHandler) HandleDisconnect fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
|
|
||||||
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _idstr;
|
|
||||||
if (isNil "_userInfo") exitWith {
|
|
||||||
[format ["(EventHandler) HandleDisconnect: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
};
|
|
||||||
|
|
||||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
|
||||||
|
|
||||||
if (_isHC) exitWith {};
|
|
||||||
|
|
||||||
[
|
|
||||||
"DisconnectedMission",
|
|
||||||
_playerID,
|
|
||||||
_playerUID,
|
|
||||||
_profileName,
|
|
||||||
_steamName,
|
|
||||||
_jip
|
|
||||||
] call attendanceTracker_fnc_logMissionEvent;
|
|
||||||
|
|
||||||
|
|
||||||
false;
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
params [["_value", "", [""]]];
|
|
||||||
("AttendanceTracker" callExtension ["getMissionHash", _value]) select 0;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
(parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0;
|
|
||||||
Binary file not shown.
@@ -18,10 +18,12 @@ class CfgFunctions {
|
|||||||
class eventHandlers {};
|
class eventHandlers {};
|
||||||
class callbackHandler {postInit = 1;};
|
class callbackHandler {postInit = 1;};
|
||||||
class log {};
|
class log {};
|
||||||
class logMissionEvent {};
|
class writeConnect {};
|
||||||
class logServerEvent {};
|
class writeDisconnect {};
|
||||||
class timestamp {};
|
class timestamp {};
|
||||||
class getHash {};
|
class getMissionHash {};
|
||||||
|
class getWorldInfo {};
|
||||||
|
class missionLoaded {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -21,11 +21,16 @@ addMissionEventHandler ["ExtensionCallback", {
|
|||||||
false;
|
false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
diag_log format ["Raw callback: %1: %2", _function, _data];
|
||||||
|
|
||||||
// Parse response from string array
|
// Parse response from string array
|
||||||
private "_response";
|
private "_response";
|
||||||
try {
|
try {
|
||||||
// diag_log format ["Raw callback: %1: %2", _function, _data];
|
// diag_log format ["Raw callback: %1: %2", _function, _data];
|
||||||
_response = parseSimpleArray _data;
|
_response = parseSimpleArray _data;
|
||||||
|
if (_response isEqualTo []) then {
|
||||||
|
throw "Failed to parse response as array";
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
[
|
[
|
||||||
format ["Callback invalid data: %1: %2: %3", _function, _data, _exception],
|
format ["Callback invalid data: %1: %2: %3", _function, _data, _exception],
|
||||||
@@ -42,17 +47,39 @@ addMissionEventHandler ["ExtensionCallback", {
|
|||||||
if (_response#0 == "SUCCESS") then {
|
if (_response#0 == "SUCCESS") then {
|
||||||
missionNamespace setVariable ["AttendanceTracker_DBConnected", true];
|
missionNamespace setVariable ["AttendanceTracker_DBConnected", true];
|
||||||
|
|
||||||
// log mission info and get back the row Id to send with future messages
|
// close any null disconnect values from previous mission
|
||||||
private _response = "AttendanceTracker" callExtension ["logMission", [
|
"AttendanceTracker" callExtension ["fillLastMissionNull", []];
|
||||||
[AttendanceTracker getVariable ["missionContext", createHashMap]] call CBA_fnc_encodeJSON
|
|
||||||
]];
|
|
||||||
AttendanceTracker_missionId = parseNumber _response;
|
|
||||||
|
|
||||||
|
|
||||||
// log world info
|
// log world info
|
||||||
private _response = "AttendanceTracker" callExtension ["logWorld", [
|
private _response = "AttendanceTracker" callExtension [
|
||||||
[call attendanceTracker_fnc_getWorldInfo] call CBA_fnc_encodeJSON
|
"logWorld",
|
||||||
]];
|
[
|
||||||
|
[(call attendanceTracker_fnc_getWorldInfo)] call CBA_fnc_encodeJSON
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// log mission info and get back the row Id to send with future messages
|
||||||
|
private _response = "AttendanceTracker" callExtension [
|
||||||
|
"logMission",
|
||||||
|
[
|
||||||
|
[AttendanceTracker getVariable ["missionContext", createHashMap]] call CBA_fnc_encodeJSON
|
||||||
|
]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "writeMissionInfo": {
|
||||||
|
if (_response#0 == "MISSION_ID") then {
|
||||||
|
AttendanceTracker_missionId = parseNumber (_response#1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "writeAttendance": {
|
||||||
|
if (_response#0 == "ATT_LOG") then {
|
||||||
|
(_response#1) params ["_eventType", "_netId", "_rowId"];
|
||||||
|
private _storeIndex = ["SERVER", "MISSION"] find _eventType;
|
||||||
|
((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [
|
||||||
|
_netId,
|
||||||
|
[nil, nil]
|
||||||
|
]) set [_storeIndex, _rowId];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default {
|
default {
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
[
|
||||||
|
["OnUserConnected", {
|
||||||
|
params ["_networkId", "_clientStateNumber", "_clientState"];
|
||||||
|
|
||||||
|
[format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
|
||||||
|
private _userInfo = (getUserInfo _networkId);
|
||||||
|
if (isNil "_userInfo") exitWith {
|
||||||
|
[format ["(EventHandler) OnUserConnected: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[format ["(EventHandler) OnUserConnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo];
|
||||||
|
|
||||||
|
[
|
||||||
|
"Server",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName,
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
] call attendanceTracker_fnc_writeConnect;
|
||||||
|
|
||||||
|
}],
|
||||||
|
["OnUserDisconnected", {
|
||||||
|
params ["_networkId", "_clientStateNumber", "_clientState"];
|
||||||
|
|
||||||
|
[format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
|
||||||
|
if !(call attendanceTracker_fnc_missionLoaded) exitWith {
|
||||||
|
[format ["(EventHandler) OnUserDisconnected: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId;
|
||||||
|
if (isNil "_userInfo") exitWith {
|
||||||
|
[format ["(EventHandler) OnUserDisconnected: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[format ["(EventHandler) OnUserDisconnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
"Server",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName
|
||||||
|
] call attendanceTracker_fnc_writeDisconnect;
|
||||||
|
}],
|
||||||
|
["PlayerConnected", {
|
||||||
|
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
||||||
|
|
||||||
|
[format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
|
||||||
|
if !(call attendanceTracker_fnc_missionLoaded) exitWith {
|
||||||
|
[format ["(EventHandler) PlayerConnected: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _userInfo = (getUserInfo _idstr);
|
||||||
|
if (isNil "_userInfo") exitWith {
|
||||||
|
[format ["(EventHandler) PlayerConnected: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[format ["(EventHandler) PlayerConnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo];
|
||||||
|
|
||||||
|
[
|
||||||
|
"Mission",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName,
|
||||||
|
_jip,
|
||||||
|
roleDescription _unit
|
||||||
|
] call attendanceTracker_fnc_writeConnect;
|
||||||
|
}],
|
||||||
|
["PlayerDisconnected", {
|
||||||
|
// NOTE: HandleDisconnect returns a DIFFERENT _id than PlayerDisconnected and above handlers, so we can't use it here
|
||||||
|
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
||||||
|
|
||||||
|
[format ["(EventHandler) HandleDisconnect fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
|
||||||
|
if !(call attendanceTracker_fnc_missionLoaded) exitWith {
|
||||||
|
[format ["(EventHandler) HandleDisconnect: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _idstr;
|
||||||
|
if (isNil "_userInfo") exitWith {
|
||||||
|
[format ["(EventHandler) HandleDisconnect: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit", "_rowId"];
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[format ["(EventHandler) HandleDisconnect: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
"Mission",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName,
|
||||||
|
_jip,
|
||||||
|
nil
|
||||||
|
] call attendanceTracker_fnc_writeDisconnect;
|
||||||
|
|
||||||
|
false;
|
||||||
|
}],
|
||||||
|
["OnUserKicked", {
|
||||||
|
params ["_networkId", "_kickTypeNumber", "_kickType", "_kickReason", "_kickMessageIncReason"];
|
||||||
|
|
||||||
|
[format ["(EventHandler) OnUserKicked fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
|
||||||
|
if !(call attendanceTracker_fnc_missionLoaded) exitWith {
|
||||||
|
[format ["(EventHandler) OnUserKicked: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId;
|
||||||
|
if (isNil "_userInfo") exitWith {
|
||||||
|
[format ["(EventHandler) OnUserKicked: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||||
|
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[format ["(EventHandler) OnUserKicked: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
"Server",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName,
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
] call attendanceTracker_fnc_writeDisconnect;
|
||||||
|
|
||||||
|
[
|
||||||
|
"Mission",
|
||||||
|
_playerID,
|
||||||
|
_playerUID,
|
||||||
|
_profileName,
|
||||||
|
_steamName,
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
] call attendanceTracker_fnc_writeDisconnect;
|
||||||
|
}]
|
||||||
|
];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
(parseSimpleArray ("AttendanceTracker" callExtension "getMissionHash")) select 0;
|
||||||
@@ -14,7 +14,7 @@ _workshopID = '';
|
|||||||
} foreach getLoadedModsInfo;
|
} foreach getLoadedModsInfo;
|
||||||
|
|
||||||
// [_name, _author, _workshopID];
|
// [_name, _author, _workshopID];
|
||||||
[
|
_return = createHashMapFromArray [
|
||||||
["author", _author],
|
["author", _author],
|
||||||
["workshopID", _workshopID],
|
["workshopID", _workshopID],
|
||||||
["displayName", _name],
|
["displayName", _name],
|
||||||
@@ -24,3 +24,5 @@ _workshopID = '';
|
|||||||
["latitude", getNumber( _world >> "latitude" )],
|
["latitude", getNumber( _world >> "latitude" )],
|
||||||
["longitude", getNumber( _world >> "longitude" )]
|
["longitude", getNumber( _world >> "longitude" )]
|
||||||
];
|
];
|
||||||
|
diag_log format ["Attendance Tracker: WorldInfo is: %1", _return];
|
||||||
|
_return
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11);
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
AttendanceTracker = false call CBA_fnc_createNamespace;
|
AttendanceTracker = false call CBA_fnc_createNamespace;
|
||||||
|
|
||||||
AttendanceTracker_missionStartTimestamp = call attendanceTracker_fnc_timestamp;
|
AttendanceTracker_missionStartTimestamp = call attendanceTracker_fnc_timestamp;
|
||||||
AttendanceTracker_missionHash = [AttendanceTracker_missionStartTimestamp] call attendanceTracker_fnc_getMissionHash;
|
diag_log format ["AttendanceTracker: Mission started at %1", AttendanceTracker_missionStartTimestamp];
|
||||||
|
AttendanceTracker_missionHash = call attendanceTracker_fnc_getMissionHash;
|
||||||
|
diag_log format ["AttendanceTracker: Mission hash is %1", AttendanceTracker_missionHash];
|
||||||
|
|
||||||
AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
||||||
["missionName", missionName],
|
["missionName", missionName],
|
||||||
@@ -13,13 +15,15 @@ AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
|||||||
["serverName", serverName],
|
["serverName", serverName],
|
||||||
["serverProfile", profileName],
|
["serverProfile", profileName],
|
||||||
["missionStart", AttendanceTracker_missionStartTimestamp],
|
["missionStart", AttendanceTracker_missionStartTimestamp],
|
||||||
["missionHash", AttendanceTracker_missionHash]
|
["missionHash", AttendanceTracker_missionHash],
|
||||||
|
["worldName", toLower worldName]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// store all user details in a hash when they connect so we can reference it in disconnect events
|
// store all user details in a hash when they connect so we can reference it in disconnect events
|
||||||
AttendanceTracker setVariable ["allUsers", createHashMap];
|
AttendanceTracker setVariable ["allUsers", createHashMap];
|
||||||
|
AttendanceTracker setVariable ["rowIds", createHashMap];
|
||||||
missionNamespace setVariable ["AttendanceTracker_debug", false];
|
missionNamespace setVariable ["AttendanceTracker_debug", false];
|
||||||
|
|
||||||
call attendanceTracker_fnc_connectDB;
|
call attendanceTracker_fnc_connectDB;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// (parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0;
|
||||||
|
|
||||||
|
// need date for MySQL in format 2006-01-02 15:04:05
|
||||||
|
|
||||||
|
systemTimeUTC params [
|
||||||
|
"_year",
|
||||||
|
"_month",
|
||||||
|
"_day",
|
||||||
|
"_hour",
|
||||||
|
"_minute",
|
||||||
|
"_second",
|
||||||
|
"_millisecond"
|
||||||
|
];
|
||||||
|
|
||||||
|
format[
|
||||||
|
"%1-%2-%3 %4:%5:%6",
|
||||||
|
_year,
|
||||||
|
_month,
|
||||||
|
_day,
|
||||||
|
_hour,
|
||||||
|
_minute,
|
||||||
|
_second
|
||||||
|
];
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ params [
|
|||||||
];
|
];
|
||||||
|
|
||||||
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
||||||
|
|
||||||
_hash set ["eventType", _eventType];
|
_hash set ["eventType", _eventType];
|
||||||
_hash set ["playerId", _playerId];
|
_hash set ["playerId", _playerId];
|
||||||
_hash set ["playerUID", _playerUID];
|
_hash set ["playerUID", _playerUID];
|
||||||
@@ -18,6 +19,6 @@ _hash set ["isJIP", _isJIP];
|
|||||||
_hash set ["roleDescription", _roleDescription];
|
_hash set ["roleDescription", _roleDescription];
|
||||||
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
||||||
|
|
||||||
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
"AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
||||||
|
|
||||||
true;
|
true;
|
||||||
@@ -3,20 +3,22 @@ params [
|
|||||||
["_playerId", ""],
|
["_playerId", ""],
|
||||||
["_playerUID", ""],
|
["_playerUID", ""],
|
||||||
["_profileName", ""],
|
["_profileName", ""],
|
||||||
["_steamName", ""]
|
["_steamName", ""],
|
||||||
|
["_isJIP", false, [true, false]],
|
||||||
|
["_roleDescription", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
||||||
|
|
||||||
_hash set ["eventType", _eventType];
|
_hash set ["eventType", _eventType];
|
||||||
_hash set ["playerId", _playerId];
|
_hash set ["playerId", _playerId];
|
||||||
_hash set ["playerUID", _playerUID];
|
_hash set ["playerUID", _playerUID];
|
||||||
_hash set ["profileName", _profileName];
|
_hash set ["profileName", _profileName];
|
||||||
_hash set ["steamName", _steamName];
|
_hash set ["steamName", _steamName];
|
||||||
_hash set ["isJIP", false];
|
_hash set ["isJIP", _isJIP];
|
||||||
_hash set ["roleDescription", ""];
|
_hash set ["roleDescription", _roleDescription];
|
||||||
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
||||||
|
|
||||||
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
"AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]];
|
||||||
|
|
||||||
true;
|
true;
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
"mysqlPort": 3306,
|
"mysqlPort": 3306,
|
||||||
"mysqlUser": "root",
|
"mysqlUser": "root",
|
||||||
"mysqlPassword": "root",
|
"mysqlPassword": "root",
|
||||||
"mysqlDatabase": "db"
|
"mysqlDatabase": "arma3_attendance"
|
||||||
}
|
}
|
||||||
105
README.md
105
README.md
@@ -1,40 +1,23 @@
|
|||||||
# 17th-attendanceTracker
|
# Arma 3 Attendance Tracker
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
**You will need a running MySQL or MariaDB instance.**
|
**You will need a running MySQL or MariaDB instance.**
|
||||||
|
|
||||||
Create a database with a name of your choosing. Then, run the following SQL command against it to create a table.
|
The following SQL commands will set up the necessary tables for the application. You can run them from the MySQL command line or from a tool like phpMyAdmin.
|
||||||
|
|
||||||
|
*In future, an ORM will be used to set this up automatically.*
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- a3server.attendancelog definition
|
CREATE DATABASE `arma3_attendance` /*!40100 DEFAULT CHARACTER SET utf8mb3 */;
|
||||||
|
|
||||||
CREATE TABLE `attendancelog` (
|
USE `arma3_attendance`;
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`timestamp` datetime NOT NULL,
|
|
||||||
`event_hash` varchar(100) NOT NULL,
|
|
||||||
`event_type` varchar(100) NOT NULL,
|
|
||||||
`player_id` varchar(30) NOT NULL,
|
|
||||||
`player_uid` varchar(100) NOT NULL,
|
|
||||||
`profile_name` varchar(100) NOT NULL,
|
|
||||||
`steam_name` varchar(100) DEFAULT NULL,
|
|
||||||
`is_jip` tinyint(4) DEFAULT NULL,
|
|
||||||
`role_description` varchar(100) DEFAULT NULL,
|
|
||||||
`mission_start` datetime NOT NULL,
|
|
||||||
`mission_name` varchar(100) DEFAULT NULL,
|
|
||||||
`briefing_name` varchar(100) DEFAULT NULL,
|
|
||||||
`mission_name_source` varchar(100) DEFAULT NULL,
|
|
||||||
`on_load_name` varchar(100) DEFAULT NULL,
|
|
||||||
`author` varchar(100) DEFAULT NULL,
|
|
||||||
`server_name` varchar(100) NOT NULL,
|
|
||||||
`server_profile` varchar(100) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=2713 DEFAULT CHARSET=utf8mb3;
|
|
||||||
|
|
||||||
-- a3server.`missions` definition
|
|
||||||
|
|
||||||
|
-- a3server.missions definition
|
||||||
CREATE TABLE `missions` (
|
CREATE TABLE `missions` (
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`world_id` int(11) DEFAULT NULL,
|
||||||
|
`mission_hash` varchar(100) NOT NULL DEFAULT '',
|
||||||
`mission_name` varchar(100) NOT NULL,
|
`mission_name` varchar(100) NOT NULL,
|
||||||
`mission_name_source` varchar(100) DEFAULT NULL,
|
`mission_name_source` varchar(100) DEFAULT NULL,
|
||||||
`briefing_name` varchar(100) DEFAULT NULL,
|
`briefing_name` varchar(100) DEFAULT NULL,
|
||||||
@@ -42,14 +25,31 @@ CREATE TABLE `missions` (
|
|||||||
`author` varchar(100) DEFAULT NULL,
|
`author` varchar(100) DEFAULT NULL,
|
||||||
`server_name` varchar(100) DEFAULT NULL,
|
`server_name` varchar(100) DEFAULT NULL,
|
||||||
`server_profile` varchar(100) DEFAULT NULL,
|
`server_profile` varchar(100) DEFAULT NULL,
|
||||||
`mission_start` datetime DEFAULT NULL,
|
`mission_start` datetime DEFAULT NULL COMMENT 'In UTC',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `mission_hash` (`mission_hash`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||||
|
|
||||||
|
-- arma3_attendance.attendance definition
|
||||||
|
CREATE TABLE `attendance` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`join_time` datetime DEFAULT NULL COMMENT 'Stored in UTC',
|
||||||
|
`disconnect_time` datetime DEFAULT NULL COMMENT 'Stored in UTC',
|
||||||
`mission_hash` varchar(100) DEFAULT NULL,
|
`mission_hash` varchar(100) DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`)
|
`event_type` varchar(100) NOT NULL,
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
|
`player_id` varchar(30) NOT NULL,
|
||||||
|
`player_uid` varchar(100) NOT NULL,
|
||||||
|
`profile_name` varchar(100) NOT NULL,
|
||||||
|
`steam_name` varchar(100) DEFAULT NULL,
|
||||||
|
`is_jip` tinyint(4) DEFAULT NULL,
|
||||||
|
`role_description` varchar(100) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `mission_hash` (`mission_hash`),
|
||||||
|
CONSTRAINT `attendance_ibfk_1` FOREIGN KEY (`mission_hash`) REFERENCES `missions` (`mission_hash`) ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||||
|
|
||||||
|
|
||||||
-- a3server.`worlds` definition
|
-- a3server.worlds definition
|
||||||
|
|
||||||
CREATE TABLE `worlds` (
|
CREATE TABLE `worlds` (
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
`author` varchar(100) DEFAULT NULL,
|
`author` varchar(100) DEFAULT NULL,
|
||||||
@@ -59,9 +59,44 @@ CREATE TABLE `worlds` (
|
|||||||
`world_size` int(11) DEFAULT NULL,
|
`world_size` int(11) DEFAULT NULL,
|
||||||
`latitude` float DEFAULT NULL,
|
`latitude` float DEFAULT NULL,
|
||||||
`longitude` float DEFAULT NULL,
|
`longitude` float DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`)
|
`workshop_id` varchar(50) DEFAULT NULL,
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `world_name` (`world_name`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3;
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, copy `config.example.json` to `config.json` and update it with your database credentials.
|
Finally, copy `config.example.json` to `config.json` and update it with your database credentials and path.
|
||||||
|
|
||||||
|
## QUERIES
|
||||||
|
|
||||||
|
### Show missions with attendance
|
||||||
|
|
||||||
|
This will retrieve a view showing all missions with attendance data, sorted by the most recent mission joins first. Mission events without a mission disconnect_time (due to server crash or in-progress mission) will be ignored.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select
|
||||||
|
a.server_profile as Server,
|
||||||
|
a.briefing_name as "Mission Name",
|
||||||
|
a.mission_start as "Start Time",
|
||||||
|
b.display_name as "World",
|
||||||
|
c.profile_name as "Player Name",
|
||||||
|
c.player_uid as "Player UID",
|
||||||
|
TIMESTAMPDIFF(
|
||||||
|
MINUTE,
|
||||||
|
c.join_time,
|
||||||
|
c.disconnect_time
|
||||||
|
) as "Play Time (m)",
|
||||||
|
c.join_time as "Join Time",
|
||||||
|
c.disconnect_time as "Leave Time"
|
||||||
|
from missions a
|
||||||
|
LEFT JOIN worlds b ON a.world_id = b.id
|
||||||
|
LEFT JOIN attendance c ON a.mission_hash = c.mission_hash
|
||||||
|
where
|
||||||
|
c.event_type = 'Mission'
|
||||||
|
AND c.disconnect_time IS NOT NULL
|
||||||
|
AND TIMESTAMPDIFF(
|
||||||
|
MINUTE,
|
||||||
|
c.join_time,
|
||||||
|
c.disconnect_time
|
||||||
|
) > 0
|
||||||
|
```
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"mysqlPort": 3306,
|
"mysqlPort": 3306,
|
||||||
"mysqlUser": "root",
|
"mysqlUser": "root",
|
||||||
"mysqlPassword": "root",
|
"mysqlPassword": "root",
|
||||||
"mysqlDatabase": "db"
|
"mysqlDatabase": "arma3_attendance"
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -1,3 +1,3 @@
|
|||||||
$ENV:GOARCH = "amd64"
|
$ENV:GOARCH = "amd64"
|
||||||
$ENV:CGO_ENABLED = 1
|
$ENV:CGO_ENABLED = 1
|
||||||
go1.16.4 build -o AttendanceTracker_x64.dll -buildmode=c-shared .
|
go1.16.4 build -o ../@AttendanceTracker/AttendanceTracker_x64.dll -buildmode=c-shared .
|
||||||
Binary file not shown.
Binary file not shown.
@@ -16,7 +16,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"path"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -32,6 +33,12 @@ var ADDON_FOLDER string = getDir() + "\\@AttendanceTracker"
|
|||||||
var LOG_FILE string = ADDON_FOLDER + "\\attendanceTracker.log"
|
var LOG_FILE string = ADDON_FOLDER + "\\attendanceTracker.log"
|
||||||
var CONFIG_FILE string = ADDON_FOLDER + "\\config.json"
|
var CONFIG_FILE string = ADDON_FOLDER + "\\config.json"
|
||||||
|
|
||||||
|
var ATTENDANCE_TABLE string = "attendance"
|
||||||
|
var MISSIONS_TABLE string = "missions"
|
||||||
|
var WORLDS_TABLE string = "worlds"
|
||||||
|
|
||||||
|
// ! TODO make a hash to save key:netId from A3 value:rowId from join event
|
||||||
|
|
||||||
var ATConfig AttendanceTrackerConfig
|
var ATConfig AttendanceTrackerConfig
|
||||||
|
|
||||||
type AttendanceTrackerConfig struct {
|
type AttendanceTrackerConfig struct {
|
||||||
@@ -104,14 +111,14 @@ func loadConfig() {
|
|||||||
writeLog(functionName, `["Config loaded", "INFO"]`)
|
writeLog(functionName, `["Config loaded", "INFO"]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMissionHash(time string) string {
|
func getMissionHash() string {
|
||||||
functionName := "getMissionHash"
|
functionName := "getMissionHash"
|
||||||
// get md5 hash of string
|
// get md5 hash of string
|
||||||
// https://stackoverflow.com/questions/2377881/how-to-get-a-md5-hash-from-a-string-in-golang
|
// https://stackoverflow.com/questions/2377881/how-to-get-a-md5-hash-from-a-string-in-golang
|
||||||
hash := md5.Sum([]byte(time))
|
hash := md5.Sum([]byte(time.Now().UTC().Format("2006-01-02 15:04:05")))
|
||||||
|
|
||||||
// convert to string
|
// convert to string
|
||||||
hashString := fmt.Sprintf("%x", hash)
|
hashString := fmt.Sprintf(`%x`, hash)
|
||||||
writeLog(functionName, fmt.Sprintf(`["Mission hash: %s", "INFO"]`, hashString))
|
writeLog(functionName, fmt.Sprintf(`["Mission hash: %s", "INFO"]`, hashString))
|
||||||
return hashString
|
return hashString
|
||||||
}
|
}
|
||||||
@@ -147,7 +154,7 @@ func connectDB() string {
|
|||||||
return "ERROR"
|
return "ERROR"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect and check the server version
|
// Check the server version
|
||||||
var version string
|
var version string
|
||||||
err = db.QueryRow("SELECT VERSION()").Scan(&version)
|
err = db.QueryRow("SELECT VERSION()").Scan(&version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -172,24 +179,29 @@ type WorldInfo struct {
|
|||||||
|
|
||||||
func writeWorldInfo(worldInfo string) {
|
func writeWorldInfo(worldInfo string) {
|
||||||
functionName := "writeWorldInfo"
|
functionName := "writeWorldInfo"
|
||||||
|
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, worldInfo))
|
||||||
// worldInfo is json, parse it
|
// worldInfo is json, parse it
|
||||||
var wi WorldInfo
|
var wi WorldInfo
|
||||||
err := json.Unmarshal([]byte(worldInfo), &wi)
|
fixedString := fixEscapeQuotes(trimQuotes(worldInfo))
|
||||||
|
err := json.Unmarshal([]byte(fixedString), &wi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write to log
|
// write to log as json
|
||||||
writeLog(functionName, fmt.Sprintf(`["Author:%s WorkshopID:%s DisplayName:%s WorldName:%s WorldNameOriginal:%s WorldSize:%d Latitude:%f Longitude:%f", "INFO"]`, wi.Author, wi.WorkshopID, wi.DisplayName, wi.WorldName, wi.WorldNameOriginal, wi.WorldSize, wi.Latitude, wi.Longitude))
|
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, json.Marshal(wi)))
|
||||||
|
|
||||||
// write to database
|
// write to database
|
||||||
// check if world exists
|
// check if world exists
|
||||||
var worldID int
|
var worldID int
|
||||||
err = db.QueryRow("SELECT id FROM worlds WHERE workshop_id = ?", wi.WorkshopID).Scan(&worldID)
|
err = db.QueryRow("SELECT id FROM worlds WHERE world_name = ?", wi.WorldName).Scan(&worldID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
// world does not exist, insert it
|
// world does not exist, insert it
|
||||||
stmt, err := db.Prepare("INSERT INTO worlds (author, workshop_id, display_name, world_name, world_name_original, world_size, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
stmt, err := db.Prepare(fmt.Sprintf(
|
||||||
|
"INSERT INTO %s (author, workshop_id, display_name, world_name, world_name_original, world_size, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
WORLDS_TABLE,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
@@ -212,7 +224,10 @@ func writeWorldInfo(worldInfo string) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// world exists, update it
|
// world exists, update it
|
||||||
stmt, err := db.Prepare("UPDATE worlds SET author = ?, workshop_id = ?, display_name = ?, world_name = ?, world_name_original = ?, world_size = ?, latitude = ?, longitude = ? WHERE id = ?")
|
stmt, err := db.Prepare(fmt.Sprintf(
|
||||||
|
"UPDATE %s SET author = ?, workshop_id = ?, display_name = ?, world_name = ?, world_name_original = ?, world_size = ?, latitude = ?, longitude = ? WHERE id = ?",
|
||||||
|
WORLDS_TABLE,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
@@ -242,48 +257,74 @@ type MissionInfo struct {
|
|||||||
ServerProfile string `json:"serverProfile"`
|
ServerProfile string `json:"serverProfile"`
|
||||||
MissionStart string `json:"missionStart"`
|
MissionStart string `json:"missionStart"`
|
||||||
MissionHash string `json:"missionHash"`
|
MissionHash string `json:"missionHash"`
|
||||||
|
WorldName string `json:"worldName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeMissionInfo(missionInfo string) {
|
func writeMissionInfo(missionInfo string) {
|
||||||
functionName := "writeMissionInfo"
|
functionName := "writeMissionInfo"
|
||||||
|
var err error
|
||||||
|
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, missionInfo))
|
||||||
// missionInfo is json, parse it
|
// missionInfo is json, parse it
|
||||||
var mi MissionInfo
|
var mi MissionInfo
|
||||||
err := json.Unmarshal([]byte(missionInfo), &mi)
|
fixedString := fixEscapeQuotes(trimQuotes(missionInfo))
|
||||||
|
err = json.Unmarshal([]byte(fixedString), &mi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get MySQL friendly datetime
|
// check if mission exists based on hash
|
||||||
// first, convert string to int
|
var worldID int
|
||||||
missionStartTime, err := strconv.ParseInt(mi.MissionStart, 10, 64)
|
err = db.QueryRow("SELECT id FROM worlds WHERE world_name = ?", mi.WorldName).Scan(&worldID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t := time.Unix(0, missionStartTime).Format("2006-01-02 15:04:05")
|
|
||||||
// write to log
|
|
||||||
writeLog(functionName, fmt.Sprintf(`["MissionName:%s BriefingName:%s MissionNameSource:%s OnLoadName:%s Author:%s ServerName:%s ServerProfile:%s MissionStart:%s MissionHash:%s", "INFO"]`, mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, t, mi.MissionHash))
|
|
||||||
|
|
||||||
// write to database
|
var stmt *sql.Stmt
|
||||||
// every mission is unique, so insert it
|
var res sql.Result
|
||||||
stmt, err := db.Prepare("INSERT INTO missions (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
||||||
|
if worldID != 0 {
|
||||||
|
sqlWorld := fmt.Sprintf(
|
||||||
|
"INSERT INTO %s (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash, world_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
MISSIONS_TABLE,
|
||||||
|
)
|
||||||
|
stmt, err = db.Prepare(sqlWorld)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
res, err := stmt.Exec(mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, t, mi.MissionStart, mi.MissionHash)
|
|
||||||
|
res, err = stmt.Exec(mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, mi.MissionStart, mi.MissionHash, worldID)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// if no world was found, write without it
|
||||||
|
sqlNoWorld := fmt.Sprintf(
|
||||||
|
"INSERT INTO %s (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
MISSIONS_TABLE,
|
||||||
|
)
|
||||||
|
stmt, err = db.Prepare(sqlNoWorld)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
res, err = stmt.Exec(mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, mi.MissionStart, mi.MissionHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
lastID, err := res.LastInsertId()
|
lastID, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeLog(functionName, fmt.Sprintf(`["Mission inserted with ID %d", "INFO"]`, lastID))
|
writeLog(functionName, fmt.Sprintf(`["Mission inserted with ID %d", "INFO"]`, lastID))
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["MISSION_ID", "%d"]`, lastID))
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttendanceLogItem struct {
|
type AttendanceLogItem struct {
|
||||||
@@ -299,17 +340,18 @@ type AttendanceLogItem struct {
|
|||||||
|
|
||||||
func writeAttendance(data string) {
|
func writeAttendance(data string) {
|
||||||
functionName := "writeAttendance"
|
functionName := "writeAttendance"
|
||||||
|
var err error
|
||||||
// data is json, parse it
|
// data is json, parse it
|
||||||
stringjson := fixEscapeQuotes(trimQuotes(data))
|
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||||
var event AttendanceLogItem
|
var event AttendanceLogItem
|
||||||
err := json.Unmarshal([]byte(stringjson), &event)
|
err = json.Unmarshal([]byte(stringjson), &event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get MySQL friendly NOW
|
// get MySQL friendly NOW
|
||||||
now := time.Now().Format("2006-01-02 15:04:05")
|
now := time.Now().UTC().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
// prevent crash
|
// prevent crash
|
||||||
if db == nil {
|
if db == nil {
|
||||||
@@ -318,8 +360,33 @@ func writeAttendance(data string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send to DB
|
// send to DB
|
||||||
|
var result sql.Result
|
||||||
|
|
||||||
result, err := db.ExecContext(context.Background(), `INSERT INTO AttendanceLog (event_time, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
if event.EventType == "Server" {
|
||||||
|
sql := fmt.Sprintf(
|
||||||
|
`INSERT INTO %s (join_time, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
ATTENDANCE_TABLE,
|
||||||
|
)
|
||||||
|
result, err = db.ExecContext(
|
||||||
|
context.Background(),
|
||||||
|
sql,
|
||||||
|
now,
|
||||||
|
event.EventType,
|
||||||
|
event.PlayerId,
|
||||||
|
event.PlayerUID,
|
||||||
|
event.ProfileName,
|
||||||
|
event.SteamName,
|
||||||
|
event.IsJIP,
|
||||||
|
event.RoleDescription,
|
||||||
|
)
|
||||||
|
} else if event.EventType == "Mission" {
|
||||||
|
sql := fmt.Sprintf(
|
||||||
|
`INSERT INTO %s (join_time, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
ATTENDANCE_TABLE,
|
||||||
|
)
|
||||||
|
result, err = db.ExecContext(
|
||||||
|
context.Background(),
|
||||||
|
sql,
|
||||||
now,
|
now,
|
||||||
event.EventType,
|
event.EventType,
|
||||||
event.PlayerId,
|
event.PlayerId,
|
||||||
@@ -330,6 +397,7 @@ func writeAttendance(data string) {
|
|||||||
event.RoleDescription,
|
event.RoleDescription,
|
||||||
event.MissionHash,
|
event.MissionHash,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
@@ -343,9 +411,124 @@ func writeAttendance(data string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeLog(functionName, fmt.Sprintf(`["Saved attendance for %s to row id %d", "INFO"]`, event.ProfileName, id))
|
writeLog(functionName, fmt.Sprintf(`["Saved attendance for %s to row id %d", "INFO"]`, event.ProfileName, id))
|
||||||
|
if event.EventType == "Server" {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["ATT_LOG", ["SERVER", "%s", "%d"]]`, event.PlayerId, id))
|
||||||
|
} else if event.EventType == "Mission" {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["ATT_LOG", ["MISSION", "%s", "%d"]]`, event.PlayerId, id))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DisconnectItem struct {
|
||||||
|
EventType string `json:"eventType"`
|
||||||
|
PlayerId string `json:"playerId"`
|
||||||
|
MissionHash string `json:"missionHash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDisconnectEvent(data string) {
|
||||||
|
functionName := "writeDisconnectEvent"
|
||||||
|
// data is json, parse it
|
||||||
|
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||||
|
var event DisconnectItem
|
||||||
|
err := json.Unmarshal([]byte(stringjson), &event)
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get MySQL friendly NOW
|
||||||
|
now := time.Now().UTC().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
// prevent crash
|
||||||
|
if db == nil {
|
||||||
|
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, check if a row exists for this player
|
||||||
|
var sql string
|
||||||
|
if event.EventType == "Mission" {
|
||||||
|
sql = fmt.Sprintf(
|
||||||
|
`
|
||||||
|
SELECT id FROM attendance
|
||||||
|
WHERE player_id = '%s' and event_type = '%s' and mission_hash = '%s' and disconnect_time IS NULL and join_time >= (NOW() - INTERVAL 24 hour)
|
||||||
|
ORDER BY join_time DESC
|
||||||
|
`,
|
||||||
|
event.PlayerId,
|
||||||
|
event.EventType,
|
||||||
|
event.MissionHash,
|
||||||
|
)
|
||||||
|
} else if event.EventType == "Server" {
|
||||||
|
sql = fmt.Sprintf(
|
||||||
|
`
|
||||||
|
SELECT id FROM attendance
|
||||||
|
WHERE player_id = '%s' and event_type = '%s' and disconnect_time IS NULL and join_time >= (NOW() - INTERVAL 24 hour)
|
||||||
|
ORDER BY join_time DESC
|
||||||
|
`,
|
||||||
|
event.PlayerId,
|
||||||
|
event.EventType,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["Unknown event type %s", "ERROR"]`, event.EventType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.QueryContext(context.Background(), sql)
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// if there is a row, update it
|
||||||
|
if rows.Next() {
|
||||||
|
// create interface to hold values
|
||||||
|
var rowId int64
|
||||||
|
|
||||||
|
err = rows.Scan(&rowId)
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the row
|
||||||
|
sql = fmt.Sprintf(
|
||||||
|
`UPDATE attendance SET disconnect_time = '%s' WHERE id = %d`,
|
||||||
|
now,
|
||||||
|
rowId,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := db.ExecContext(context.Background(), sql)
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["Saved disconnect event for %s to row id %d", "INFO"]`, event.PlayerId, rowId))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// otherwise, log an error
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["No row found for %s, %s", "ERROR"]`, event.PlayerId, event.EventType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillLastMissionNull() {
|
||||||
|
functionName := "fillLastMissionNull"
|
||||||
|
// prevent crash
|
||||||
|
if db == nil {
|
||||||
|
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sql := `call proc_filllastmissionnull`
|
||||||
|
|
||||||
|
_, err := db.ExecContext(context.Background(), sql)
|
||||||
|
if err != nil {
|
||||||
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeLog(functionName, `["Filled mission event NULLs", "INFO"]`)
|
||||||
|
}
|
||||||
|
|
||||||
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
||||||
return C.runExtensionCallback(extensionCallbackFnc, name, function, data)
|
return C.runExtensionCallback(extensionCallbackFnc, name, function, data)
|
||||||
}
|
}
|
||||||
@@ -374,12 +557,23 @@ func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv
|
|||||||
temp := fmt.Sprintf("Function: %s nb params: %d", C.GoString(input), argc)
|
temp := fmt.Sprintf("Function: %s nb params: %d", C.GoString(input), argc)
|
||||||
|
|
||||||
switch C.GoString(input) {
|
switch C.GoString(input) {
|
||||||
case "logAttendance":
|
case "fillLastMissionNull":
|
||||||
{ // callExtension ["serverEvent", [_hash] call CBA_fnc_encodeJSON];
|
{
|
||||||
|
go fillLastMissionNull()
|
||||||
|
}
|
||||||
|
case "writeAttendance":
|
||||||
|
{ // callExtension ["logAttendance", [_hash] call CBA_fnc_encodeJSON]];
|
||||||
if argc == 1 {
|
if argc == 1 {
|
||||||
go writeAttendance(out[0])
|
go writeAttendance(out[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "writeDisconnectEvent":
|
||||||
|
{ // callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]];
|
||||||
|
|
||||||
|
if argc == 1 {
|
||||||
|
go writeDisconnectEvent(out[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
case "logMission":
|
case "logMission":
|
||||||
if argc == 1 {
|
if argc == 1 {
|
||||||
go writeMissionInfo(out[0])
|
go writeMissionInfo(out[0])
|
||||||
@@ -388,12 +582,6 @@ func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv
|
|||||||
if argc == 1 {
|
if argc == 1 {
|
||||||
go writeWorldInfo(out[0])
|
go writeWorldInfo(out[0])
|
||||||
}
|
}
|
||||||
case "getMissionHash":
|
|
||||||
{
|
|
||||||
if argc == 1 {
|
|
||||||
go getMissionHash(out[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a result to Arma
|
// Return a result to Arma
|
||||||
@@ -421,9 +609,10 @@ func callBackExample() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTimestamp() int64 {
|
func getTimestamp() string {
|
||||||
// get the current unix timestamp in nanoseconds
|
// get the current unix timestamp in nanoseconds
|
||||||
return time.Now().UnixNano()
|
// return time.Now().Local().Unix()
|
||||||
|
return time.Now().UTC().Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimQuotes(s string) string {
|
func trimQuotes(s string) string {
|
||||||
@@ -445,6 +634,9 @@ func writeLog(functionName string, data string) {
|
|||||||
defer C.free(unsafe.Pointer(statusParam))
|
defer C.free(unsafe.Pointer(statusParam))
|
||||||
runExtensionCallback(statusName, statusFunction, statusParam)
|
runExtensionCallback(statusName, statusFunction, statusParam)
|
||||||
|
|
||||||
|
// get calling function & line
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
log.Printf(`%s:%d: %s`, path.Base(file), line, data)
|
||||||
log.Printf(`%s: %s`, functionName, data)
|
log.Printf(`%s: %s`, functionName, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,12 +653,12 @@ func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
|
|||||||
case "getDir":
|
case "getDir":
|
||||||
temp = getDir()
|
temp = getDir()
|
||||||
case "getTimestamp":
|
case "getTimestamp":
|
||||||
time := getTimestamp()
|
temp = fmt.Sprintf(`["%s"]`, getTimestamp())
|
||||||
temp = fmt.Sprintf(`["%s"]`, strconv.FormatInt(time, 10))
|
|
||||||
case "connectDB":
|
case "connectDB":
|
||||||
go connectDB()
|
go connectDB()
|
||||||
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
|
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
|
||||||
|
case "getMissionHash":
|
||||||
|
temp = fmt.Sprintf(`["%s"]`, getMissionHash())
|
||||||
default:
|
default:
|
||||||
temp = fmt.Sprintf(`["%s"]`, "Unknown Function")
|
temp = fmt.Sprintf(`["%s"]`, "Unknown Function")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
freeExtension "AttendanceTracker";
|
|
||||||
"AttendanceTracker" callExtension "connectDB";
|
|
||||||
sleep 2;
|
|
||||||
"attendanceTracker" callExtension ["logAttendance", ["{""playerUID"": ""76561197991996737"", ""roleDescription"": ""NULL"", ""missionNameSource"": ""aaaltisaiatk"", ""eventType"": ""ConnectedMission"", ""briefingName"": ""aaaltisaiatk"", ""profileName"": ""IndigoFox"", ""serverName"": ""IndigoFox on DESKTOP-6B2U0AT"", ""steamName"": ""IndigoFox"", ""onLoadName"": ""NULL"", ""missionName"": ""aaaltisaiatk"", ""isJIP"": false, ""author"": ""IndigoFox"", ""missionStart"": ""1682549469590908300""}"]]
|
|
||||||
"attendanceTracker" callExtension ["logAttendance", ["{""playerUID"": ""76561197991996737"", ""roleDescription"": ""NULL"", ""missionNameSource"": ""aaaltisaiatk"", ""eventType"": ""ConnectedServer"", ""briefingName"": ""aaaltisaiatk"", ""profileName"": ""IndigoFox"", ""serverName"": ""IndigoFox on DESKTOP-6B2U0AT"", ""steamName"": ""IndigoFox"", ""onLoadName"": ""NULL"", ""missionName"": ""aaaltisaiatk"", ""isJIP"": false, ""author"": ""IndigoFox"", ""missionStart"": ""1682549469590908300""}"]]
|
|
||||||
sleep 15;
|
|
||||||
exit;
|
|
||||||
Reference in New Issue
Block a user