Common - Improve PBO checking (#9266)

* Update PBO checking

* Added kicking of clients without ACE loaded

* Update fnc_errorMessage.sqf

* Update fnc_checkVersionNumber.sqf

* More compatibility for #9568

* Cleanup

* Minor cleanup + added server source

* update outdated/not present error message

* check version number fixes

* Update fnc_errorMessage.sqf

* Changed error names

Server is always right, client has either older or newer versions, or missing or additional addons

* Improved ACE detection method

* Tweaks and fixes

* Try another approach

* Update events-framework.md

* Update XEH_postInit.sqf

* Update fnc_checkVersionNumber.sqf

* Removed check for non-ACE clients

* Update XEH_postInit.sqf

* Cleanup

* Remove rogue change

* Improved message display in systemChat

* Update fnc_checkPBOs.sqf

* Removed loop variable initialisers

* Fixed header

* Updated headers

---------

Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com>
Co-authored-by: LinkIsGrim <salluci.lovi@gmail.com>
This commit is contained in:
johnb432 2024-06-22 15:52:59 +02:00 committed by GitHub
parent db6c4a72a6
commit 7ea2aab2c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 408 additions and 373 deletions

View File

@ -30,6 +30,7 @@ PREP(changeProjectileDirection);
PREP(checkFiles); PREP(checkFiles);
PREP(checkFiles_diagnoseACE); PREP(checkFiles_diagnoseACE);
PREP(checkPBOs); PREP(checkPBOs);
PREP(checkVersionNumber);
PREP(claim); PREP(claim);
PREP(claimSafeServer); PREP(claimSafeServer);
PREP(codeToString); PREP(codeToString);

View File

@ -15,15 +15,20 @@
* Public: No * Public: No
*/ */
/////////////// // Don't execute in scheduled environment
// check addons if (canSuspend) exitWith {
/////////////// [FUNC(checkFiles), nil] call CBA_fnc_directCall;
private _mainCfg = configFile >> "CfgPatches" >> "ace_main"; };
private _mainVersion = getText (_mainCfg >> "versionStr");
private _mainSource = configSourceMod _mainCfg;
//CBA Versioning check - close main display if using incompatible version ///////////////
private _cbaVersionAr = getArray (configFile >> "CfgPatches" >> "cba_main" >> "versionAr"); // Check addons
///////////////
private _cfgPatches = configFile >> "CfgPatches";
private _mainVersion = getText (_cfgPatches >> "ace_main" >> "versionStr");
private _mainSource = configSourceMod (_cfgPatches >> "ace_main");
// CBA Versioning check - close main display if using incompatible version
private _cbaVersionAr = getArray (_cfgPatches >> "cba_main" >> "versionAr");
private _cbaRequiredAr = getArray (configFile >> "CfgSettings" >> "CBA" >> "Versioning" >> "ACE" >> "dependencies" >> "CBA") select 1; private _cbaRequiredAr = getArray (configFile >> "CfgSettings" >> "CBA" >> "Versioning" >> "ACE" >> "dependencies" >> "CBA") select 1;
private _cbaVersionStr = _cbaVersionAr joinString "."; private _cbaVersionStr = _cbaVersionAr joinString ".";
@ -31,53 +36,62 @@ private _cbaRequiredStr = _cbaRequiredAr joinString ".";
INFO_3("ACE is version %1 - CBA is version %2 (min required %3)",_mainVersion,_cbaVersionStr,_cbaRequiredStr); INFO_3("ACE is version %1 - CBA is version %2 (min required %3)",_mainVersion,_cbaVersionStr,_cbaRequiredStr);
if ([_cbaRequiredAr, _cbaVersionAr] call cba_versioning_fnc_version_compare) then { if ([_cbaRequiredAr, _cbaVersionAr] call CBA_versioning_fnc_version_compare) then {
private _errorMsg = format ["CBA version %1 is outdated (required %2)", _cbaVersionStr, _cbaRequiredStr]; private _errorMsg = format ["CBA version %1 is outdated (required %2)", _cbaVersionStr, _cbaRequiredStr];
ERROR(_errorMsg); ERROR(_errorMsg);
if (hasInterface) then { if (hasInterface) then {
["[ACE] ERROR", _errorMsg, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage); ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
}; };
}; };
//private _addons = activatedAddons; // broken with High-Command module, see #2134 //private _addons = activatedAddons; // Broken with High-Command module, see #2134
private _addons = (cba_common_addons select {(_x select [0,4]) == "ace_"}) apply {toLowerANSI _x}; private _addons = (CBA_common_addons select {(_x select [0, 4]) == "ace_"}) apply {toLowerANSI _x};
private _oldAddons = []; private _oldAddons = [];
private _oldSources = []; private _oldSources = [];
private _oldCompats = []; private _oldCompats = [];
{ {
private _addonCfg = configFile >> "CfgPatches" >> _x; private _addonCfg = configFile >> "CfgPatches" >> _x;
private _addonVersion = getText (_addonCfg >> "versionStr"); private _addonVersion = getText (_addonCfg >> "versionStr");
if (_addonVersion != _mainVersion) then { if (_addonVersion != _mainVersion) then {
private _addonSource = configSourceMod _addonCfg; private _addonSource = configSourceMod _addonCfg;
_oldSources pushBackUnique _addonSource; _oldSources pushBackUnique _addonSource;
// Check ACE install
call FUNC(checkFiles_diagnoseACE); call FUNC(checkFiles_diagnoseACE);
// Don't block game if it's just an old compat pbo
if ((_x select [0, 10]) != "ace_compat") then { if ((_x select [0, 10]) != "ace_compat") then {
if (hasInterface) then { _oldAddons pushBack _x;
_oldAddons pushBack _x;
};
} else { } else {
_oldCompats pushBack [_x, _addonVersion]; // Don't block game if it's just an old compat pbo _oldCompats pushBack [_x, _addonVersion];
}; };
}; };
} forEach _addons; } forEach _addons;
if (_oldAddons isNotEqualTo []) then { if (_oldAddons isNotEqualTo []) then {
_oldAddons = _oldAddons apply { format ["%1.pbo", _x] }; _oldAddons = _oldAddons apply {format ["%1.pbo", _x]};
private _errorMsg = "";
if (count _oldAddons > 3) then { private _errorMsg = if (count _oldAddons > 3) then {
_errorMsg = format ["The following files are outdated: %1, and %2 more.<br/>ACE Main version is %3 from %4.<br/>Loaded mods with outdated ACE files: %5", (_oldAddons select [0, 3]) joinString ", ", (count _oldAddons) -3, _mainVersion, _mainSource, (_oldSources joinString ", ")]; format ["The following files are outdated: %1, and %2 more.<br/>ACE Main version is %3 from %4.<br/>Loaded mods with outdated ACE files: %5", (_oldAddons select [0, 3]) joinString ", ", (count _oldAddons) - 3, _mainVersion, _mainSource, _oldSources joinString ", "];
} else { } else {
_errorMsg = format ["The following files are outdated: %1.<br/>ACE Main version is %2 from %3.<br/>Loaded mods with outdated ACE files: %4", (_oldAddons) joinString ", ", _mainVersion, _mainSource, (_oldSources) joinString ", "]; format ["The following files are outdated: %1.<br/>ACE Main version is %2 from %3.<br/>Loaded mods with outdated ACE files: %4", _oldAddons joinString ", ", _mainVersion, _mainSource, _oldSources joinString ", "];
}; };
if (hasInterface) then { if (hasInterface) then {
["[ACE] ERROR", _errorMsg, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage); ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
}; };
ERROR(_errorMsg); ERROR(_errorMsg);
}; };
if (_oldCompats isNotEqualTo []) then { if (_oldCompats isNotEqualTo []) then {
_oldCompats = _oldCompats apply {format ["%1 (%2)", _x select 0, _x select 1]}; _oldCompats = _oldCompats apply {format ["%1 (%2)", _x select 0, _x select 1]};
[{ [{
// Lasts for ~10 seconds // Lasts for ~10 seconds
ERROR_WITH_TITLE_3("The following ACE compatiblity PBOs are outdated","%1. ACE Main version is %2 from %3.",_this select 0,_this select 1,_this select 2); ERROR_WITH_TITLE_3("The following ACE compatiblity PBOs are outdated","%1. ACE Main version is %2 from %3.",_this select 0,_this select 1,_this select 2);
@ -85,9 +99,10 @@ if (_oldCompats isNotEqualTo []) then {
}; };
/////////////// ///////////////
// check extensions // Check extensions
/////////////// ///////////////
private _platform = toLowerANSI (productVersion select 6); private _platform = toLowerANSI (productVersion select 6);
if (!isServer && {_platform in ["linux", "osx"]}) then { if (!isServer && {_platform in ["linux", "osx"]}) then {
// Linux and OSX client ports do not support extensions at all // Linux and OSX client ports do not support extensions at all
INFO("Operating system does not support extensions"); INFO("Operating system does not support extensions");
@ -101,8 +116,10 @@ if (!isServer && {_platform in ["linux", "osx"]}) then {
if ((_isWindows || _isLinux) && {_isClient || _isServer}) then { if ((_isWindows || _isLinux) && {_isClient || _isServer}) then {
private _versionEx = _extension callExtension "version"; private _versionEx = _extension callExtension "version";
if (_versionEx == "") then { if (_versionEx == "") then {
private _extensionFile = _extension; private _extensionFile = _extension;
if (productVersion select 7 == "x64") then { if (productVersion select 7 == "x64") then {
_extensionFile = format ["%1_x64", _extensionFile]; _extensionFile = format ["%1_x64", _extensionFile];
}; };
@ -114,7 +131,7 @@ if (!isServer && {_platform in ["linux", "osx"]}) then {
ERROR(_errorMsg); ERROR(_errorMsg);
if (hasInterface) then { if (hasInterface) then {
["[ACE] ERROR", _errorMsg, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage); ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
}; };
} else { } else {
// Print the current extension version // Print the current extension version
@ -123,54 +140,66 @@ if (!isServer && {_platform in ["linux", "osx"]}) then {
}; };
} forEach ("true" configClasses (configFile >> "ACE_Extensions")); } forEach ("true" configClasses (configFile >> "ACE_Extensions"));
}; };
if (isArray (configFile >> "ACE_Extensions" >> "extensions")) then { if (isArray (configFile >> "ACE_Extensions" >> "extensions")) then {
WARNING("extensions[] array no longer supported"); WARNING("extensions[] array no longer supported");
}; };
/////////////// ///////////////
// check server version/addons // Check server version/addons
/////////////// ///////////////
if (isMultiplayer) then { if (isMultiplayer) then {
// don't check optional addons // Don't check optional addons
_addons = _addons select {getNumber (configFile >> "CfgPatches" >> _x >> "ACE_isOptional") != 1}; _addons = _addons select {getNumber (_cfgPatches >> _x >> "ACE_isOptional") != 1};
if (isServer) then { if (isServer) then {
// send servers version of ACE to all clients // Send server's version of ACE to all clients
GVAR(ServerVersion) = _mainVersion; GVAR(serverVersion) = _mainVersion;
GVAR(ServerAddons) = _addons; GVAR(serverAddons) = _addons;
publicVariable QGVAR(ServerVersion); GVAR(serverSource) = _mainSource;
publicVariable QGVAR(ServerAddons);
publicVariable QGVAR(serverVersion);
publicVariable QGVAR(serverAddons);
publicVariable QGVAR(serverSource);
} else { } else {
// clients have to wait for the variables GVAR(clientVersion) = _version;
[{ GVAR(clientAddons) = _addons;
if (isNil QGVAR(ServerVersion) || isNil QGVAR(ServerAddons)) exitWith {};
(_this select 0) params ["_mainVersion", "_addons"]; private _fnc_check = {
if (GVAR(clientVersion) != GVAR(serverVersion)) then {
if (_mainVersion != GVAR(ServerVersion)) then { private _errorMsg = format ["Client/Server Version Mismatch. Server: %1, Client: %2. Server modDir: %3", GVAR(serverVersion), GVAR(clientVersion), GVAR(serverSource)];
private _errorMsg = format ["Client/Server Version Mismatch. Server: %1, Client: %2.", GVAR(ServerVersion), _mainVersion];
// Check ACE install
call FUNC(checkFiles_diagnoseACE); call FUNC(checkFiles_diagnoseACE);
ERROR(_errorMsg); ERROR(_errorMsg);
if (hasInterface) then { if (hasInterface) then {
["[ACE] ERROR", _errorMsg, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage); ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
}; };
}; };
_addons = _addons - GVAR(ServerAddons); private _addons = GVAR(clientAddons) - GVAR(serverAddons);
if (_addons isNotEqualTo []) then { if (_addons isNotEqualTo []) then {
private _errorMsg = format ["Client/Server Addon Mismatch. Client has extra addons: %1.",_addons]; private _errorMsg = format ["Client/Server Addon Mismatch. Client has additional addons: %1. Server modDir: %2", _addons, GVAR(serverSource)];
// Check ACE install
call FUNC(checkFiles_diagnoseACE); call FUNC(checkFiles_diagnoseACE);
ERROR(_errorMsg); ERROR(_errorMsg);
if (hasInterface) then { if (hasInterface) then {
["[ACE] ERROR", _errorMsg, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage); ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
}; };
}; };
};
[_this select 1] call CBA_fnc_removePerFrameHandler; // Clients have to wait for the variables
}, 1, [_mainVersion,_addons]] call CBA_fnc_addPerFrameHandler; if (isNil QGVAR(serverVersion) || isNil QGVAR(serverAddons)) then {
GVAR(serverVersion) addPublicVariableEventHandler _fnc_check;
} else {
call _fnc_check;
};
}; };
}; };

View File

@ -1,13 +1,13 @@
#include "..\script_component.hpp" #include "..\script_component.hpp"
/* /*
* Author: PabstMirror * Author: PabstMirror
* Diagnose ACE install problems, this will only be called if there is a known problem * Diagnoses ACE install problems, this will only be called if there is a known problem.
* *
* Arguments: * Arguments:
* None * None
* *
* Return Value: * Return Value:
* None * ACE addons' WS IDs <HASHMAP>
* *
* Example: * Example:
* [] call ace_common_fnc_checkFiles_diagnoseACE * [] call ace_common_fnc_checkFiles_diagnoseACE
@ -16,43 +16,59 @@
*/ */
// Only run once // Only run once
if (missionNameSpace getVariable [QGVAR(checkFiles_diagnoseACE), false]) exitWith {}; if (missionNameSpace getVariable [QGVAR(checkFiles_diagnoseACE), false]) exitWith {
createHashMap // return
};
GVAR(checkFiles_diagnoseACE) = true; GVAR(checkFiles_diagnoseACE) = true;
private _addons = cba_common_addons select {(_x select [0,4]) == "ace_"}; private _addons = CBA_common_addons select {(_x select [0, 4]) == "ace_"};
private _cfgPatches = configFile >> "CfgPatches"; private _cfgPatches = configFile >> "CfgPatches";
private _allMods = createHashMap; private _allMods = createHashMap;
private _getLoadedModsInfo = getLoadedModsInfo;
// Check ACE_ADDONs are in expected mod DIR // Check if ACE_ADDONs are in expected mod DIR
{ {
private _cfg = (_cfgPatches >> _x); private _cfg = _cfgPatches >> _x;
private _actualModDir = configSourceMod _cfg; private _actualModDir = configSourceMod _cfg;
private _expectedModDir = getText (_cfg >> "ACE_expectedModDir"); private _expectedModDir = getText (_cfg >> "ACE_expectedModDir");
if (_expectedModDir == "") then { _expectedModDir = "@ace" };
if (_expectedModDir == "") then {
_expectedModDir = "@ace";
};
private _expectedSteamID = getText (_cfg >> "ACE_expectedSteamID"); private _expectedSteamID = getText (_cfg >> "ACE_expectedSteamID");
if (_expectedSteamID == "") then { _expectedSteamID = "463939057" };
if (_expectedSteamID == "") then {
_expectedSteamID = "463939057"
};
(_allMods getOrDefault [_actualModDir, [], true]) pushBackUnique _expectedSteamID; (_allMods getOrDefault [_actualModDir, [], true]) pushBackUnique _expectedSteamID;
if (_actualModDir != _expectedModDir) then { if (_actualModDir != _expectedModDir) then {
private _errorMsg = format ["%1 loading from unexpected modDir [%2]",_x,_actualModDir]; private _errorMsg = format ["%1 loading from unexpected modDir [%2]", _x, _actualModDir];
systemChat _errorMsg; systemChat _errorMsg;
WARNING_1("%1",_errorMsg); WARNING_1("%1",_errorMsg);
}; };
} forEach _addons; } forEach _addons;
// Check all ACE ModDirs have expected steam WS ID // Check if all ACE ModDirs have expected steam WS ID
{ {
private _modDir = _x; private _modDir = _x;
if ((count _y) != 1) then { ERROR_2("Unexpected multiple steamIDs %1 - %2",_modDir,_y) };
private _expectedSteamID = _y # 0; if (count _y != 1) then {
private _index = getLoadedModsInfo findIf {_x#1 == _modDir}; ERROR_2("Unexpected multiple steamIDs %1 - %2",_modDir,_y);
(getLoadedModsInfo param [_index, []]) params [["_modName", "$Error$"], "", "", "", "", "", "", ["_actualID", ""]]; };
private _expectedSteamID = _y select 0;
private _index = _getLoadedModsInfo findIf {_x select 1 == _modDir};
(_getLoadedModsInfo param [_index, []]) params [["_modName", "$Error$"], "", "", "", "", "", "", ["_actualID", ""]];
if (_actualID != _expectedSteamID) then { if (_actualID != _expectedSteamID) then {
private _errorMsg = format ["%1 [%2] unexpected workshopID [%3]",_modDir,_modName,_actualID]; private _errorMsg = format ["%1 [%2] unexpected workshopID [%3]", _modDir, _modName, _actualID];
systemChat _errorMsg; systemChat _errorMsg;
WARNING_1("%1",_errorMsg); WARNING_1("%1",_errorMsg);
}; };
} forEach _allMods; } forEach _allMods;
_allMods _allMods // return

View File

@ -1,6 +1,6 @@
#include "..\script_component.hpp" #include "..\script_component.hpp"
/* /*
* Author: commy2 * Author: commy2, johnb43
* Used to execute the checkPBOs module without placing the module. Don't use this together with the module. * Used to execute the checkPBOs module without placing the module. Don't use this together with the module.
* Checks PBO versions and compares to the one running on server. * Checks PBO versions and compares to the one running on server.
* *
@ -9,8 +9,8 @@
* 0 = Warn once * 0 = Warn once
* 1 = Warn permanently * 1 = Warn permanently
* 2 = Kick * 2 = Kick
* 1: Check all PBOs? (default: false) <BOOL> * 1: Check all PBOs? <BOOL> (default: false)
* 2: Whitelist (default: "") <STRING> * 2: Whitelist <STRING> (default: "")
* *
* Return Value: * Return Value:
* None * None
@ -24,7 +24,7 @@
params ["_mode", ["_checkAll", false], ["_whitelist", "", [""]]]; params ["_mode", ["_checkAll", false], ["_whitelist", "", [""]]];
TRACE_3("params",_mode,_checkAll,_whitelist); TRACE_3("params",_mode,_checkAll,_whitelist);
//lowercase and convert whiteList String into array of strings: // Lowercase and convert whiteList string into array of strings
_whitelist = toLowerANSI _whitelist; _whitelist = toLowerANSI _whitelist;
_whitelist = _whitelist splitString "[,""']"; _whitelist = _whitelist splitString "[,""']";
TRACE_1("Array",_whitelist); TRACE_1("Array",_whitelist);
@ -32,75 +32,67 @@ TRACE_1("Array",_whitelist);
ACE_Version_CheckAll = _checkAll; ACE_Version_CheckAll = _checkAll;
ACE_Version_Whitelist = _whitelist; ACE_Version_Whitelist = _whitelist;
if (!_checkAll) exitWith {}; //ACE is checked by FUNC(checkFiles) // ACE is checked by FUNC(checkFiles)
if (!_checkAll) exitWith {};
if (!isServer) then { if (!isServer) then {
[{ ["ace_versioning_clientCheckDone", {
if (isNil "ACE_Version_ClientErrors") exitWith {}; // Don't let this event get triggered again
[_thisType, _thisId] call CBA_fnc_removeEventHandler;
ACE_Version_ClientErrors params ["_missingAddon", "_missingAddonServer", "_oldVersionClient", "_oldVersionServer"]; params ["_clientErrors"];
_clientErrors params ["_missingAddonClient", "_additionalAddonClient", "_olderVersionClient", "_newerVersionClient"];
_thisArgs params ["_mode"];
(_this select 0) params ["_mode", "_checkAll", "_whitelist"]; // Display error message(s)
if (_missingAddonClient || {_additionalAddonClient} || {_olderVersionClient} || {_newerVersionClient}) then {
private _errorMsg = "[ACE] Version mismatch:<br/><br/>";
private _error = [];
// Display error message. if (_missingAddonClient) then {
if (_missingAddon || {_missingAddonServer} || {_oldVersionClient} || {_oldVersionServer}) then { _errorMsg = _errorMsg + "Detected missing addon on client<br/>";
private _text = "[ACE] Version mismatch:<br/><br/>"; _error pushBack "Missing file(s)";
private _error = format ["ACE version mismatch: %1: ", profileName];
if (_missingAddon) then {
_text = _text + "Detected missing addon on client<br/>";
_error = _error + "Missing file(s); ";
};
if (_missingAddonServer) then {
_text = _text + "Detected missing addon on server<br/>";
_error = _error + "Additional file(s); ";
};
if (_oldVersionClient) then {
_text = _text + "Detected old client version<br/>";
_error = _error + "Older version; ";
};
if (_oldVersionServer) then {
_text = _text + "Detected old server version<br/>";
_error = _error + "Newer version; ";
}; };
//[QGVAR(systemChatGlobal), _error] call CBA_fnc_globalEvent; if (_additionalAddonClient) then {
_errorMsg = _errorMsg + "Detected additional addon on client<br/>";
_error pushBack "Additional file(s)";
};
ERROR(_error); if (_olderVersionClient) then {
_errorMsg = _errorMsg + "Detected older client version<br/>";
_error pushBack "Older version";
};
if (_newerVersionClient) then {
_errorMsg = _errorMsg + "Detected newer client version<br/>";
_error pushBack "Newer version";
};
ERROR_2("[ACE] Version mismatch: %1: %2",profileName,_error joinString ", ");
_errorMsg = parseText format ["<t align='center'>%1</t>", _errorMsg];
// Warn
if (_mode < 2) then { if (_mode < 2) then {
_text = composeText [lineBreak, parseText format ["<t align='center'>%1</t>", _text]];
private _rscLayer = "ACE_RscErrorHint" call BIS_fnc_rscLayer; private _rscLayer = "ACE_RscErrorHint" call BIS_fnc_rscLayer;
_rscLayer cutRsc ["ACE_RscErrorHint", "PLAIN", 0, true]; _rscLayer cutRsc ["ACE_RscErrorHint", "PLAIN", 0, true];
disableSerialization; (uiNamespace getVariable "ACE_ctrlErrorHint") ctrlSetStructuredText composeText [lineBreak, _errorMsg];
private _ctrlHint = uiNamespace getVariable "ACE_ctrlErrorHint";
_ctrlHint ctrlSetStructuredText _text;
if (_mode == 0) then { if (_mode == 0) then {
[{ [{
params ["_rscLayer"]; TRACE_2("Hiding Error message after 10 seconds",time,_this);
TRACE_2("Hiding Error message after 10 seconds",time,_rscLayer); _this cutFadeOut 0.2;
_rscLayer cutFadeOut 0.2; }, _rscLayer, 10] call CBA_fnc_waitAndExecute;
}, [_rscLayer], 10] call CBA_fnc_waitAndExecute;
}; };
}; } else {
// Kick
if (_mode == 2) then { ["[ACE] ERROR", composeText [_errorMsg]] call FUNC(errorMessage);
[{alive player}, { // To be able to show list if using checkAll
params ["_text"];
TRACE_2("Player is alive, showing msg and exiting",time,_text);
_text = composeText [parseText format ["<t align='center'>%1</t>", _text]];
["[ACE] ERROR", _text, {findDisplay 46 closeDisplay 0}] call FUNC(errorMessage);
}, [_text]] call CBA_fnc_waitUntilAndExecute;
}; };
}; };
}, [_mode]] call CBA_fnc_addEventHandlerArgs;
[_this select 1] call CBA_fnc_removePerFrameHandler;
}, 1, [_mode, _checkAll, _whitelist]] call CBA_fnc_addPerFrameHandler;
}; };
if (_checkAll) then { // Check file version numbers
0 spawn COMPILE_FILE(scripts\checkVersionNumber); // @todo [_whitelist] call FUNC(checkVersionNumber);
};

View File

@ -0,0 +1,161 @@
#include "..\script_component.hpp"
/*
* Author: commy2, johnb43
* Compares version numbers from loaded addons.
*
* Arguments:
* 0: Lowercase addon whitelist <ARRAY> (default: missionNamespace getVariable ["ACE_Version_Whitelist", []])
*
* Return Value:
* None
*
* Example:
* call ace_common_fnc_checkVersionNumber
*
* Public: No
*/
// Don't execute in scheduled environment
if (canSuspend) exitWith {
[FUNC(checkVersionNumber), _this] call CBA_fnc_directCall;
};
params [["_whitelist", missionNamespace getVariable ["ACE_Version_Whitelist", []]]];
private _files = CBA_common_addons select {
(_x select [0, 3] != "a3_") &&
{_x select [0, 4] != "ace_"} &&
{!((toLowerANSI _x) in _whitelist)}
};
private _cfgPatches = configFile >> "CfgPatches";
private _versions = [];
{
(getText (_cfgPatches >> _x >> "version") splitString ".") params [["_major", "0"], ["_minor", "0"]];
private _version = parseNumber _major + parseNumber _minor / 100;
_versions pushBack _version;
} forEach _files;
if (isServer) exitWith {
ACE_Version_ServerVersions = [_files, _versions];
publicVariable "ACE_Version_ServerVersions";
// Raise event when done
["ace_versioning_serverCheckDone", [+ACE_Version_ServerVersions]] call CBA_fnc_localEvent;
};
// Begin client version check
ACE_Version_ClientVersions = [_files, _versions];
private _fnc_check = {
ACE_Version_ClientVersions params [["_files", []], ["_versions", []]];
ACE_Version_ServerVersions params [["_serverFiles", []], ["_serverVersions", []]];
// Compare client and server files and versions
private _client = profileName;
private _missingAddonsClient = [];
private _olderVersionsClient = [];
private _newerVersionsClient = [];
{
private _serverVersion = _serverVersions select _forEachIndex;
private _index = _files find _x;
if (_index == -1) then {
if (_x != "ace_server") then {
_missingAddonsClient pushBack _x;
};
} else {
private _clientVersion = _versions select _index;
if (_clientVersion < _serverVersion) then {
_olderVersionsClient pushBack [_x, _clientVersion, _serverVersion];
};
if (_clientVersion > _serverVersion) then {
_newerVersionsClient pushBack [_x, _clientVersion, _serverVersion];
};
};
} forEach _serverFiles;
// Find client files which the server doesn't have
private _additionalAddonsClient = _files select {!(_x in _serverFiles)};
// Check for client missing addons, server missing addons, client outdated addons and server outdated addons
private _clientErrors = [];
#define DISPLAY_NUMBER_ADDONS (10 + 1) // +1 to account for header
{
_x params ["_items", "_string"];
// Check if something is either missing or outdated
private _isMissingItems = _items isNotEqualTo [];
if (_isMissingItems) then {
// Generate error message
private _errorLog = +_items;
private _header = format ["[ACE] %1: ERROR %2 addon(s): ", _client, _string];
// Don't display all missing items, as they are logged
private _errorMsg = _header + ((_errorLog select [0, DISPLAY_NUMBER_ADDONS]) joinString ", ");
_errorLog = _header + (_errorLog joinString ", ");
private _count = count _items;
if (_count > DISPLAY_NUMBER_ADDONS) then {
_errorMsg = _errorMsg + format [", and %1 more.", _count - DISPLAY_NUMBER_ADDONS];
};
// Wait until in briefing screen
[{
getClientStateNumber >= 9 // "BRIEFING SHOWN"
}, {
params ["_errorLog", "_errorMsg"];
// Log and display error messages
diag_log text _errorLog;
[QGVAR(serverLog), _errorLog] call CBA_fnc_serverEvent;
[QGVAR(systemChatGlobal), _errorMsg] call CBA_fnc_globalEvent;
// Wait until after map screen
[{
!isNull (call BIS_fnc_displayMission)
}, {
params ["_errorMsg", "_timeOut"];
// If the briefing screen was shown for less than 5 seconds, display the error message again, but locally
if (_timeOut < CBA_missionTime) exitWith {};
// Make sure systemChat is ready by waiting a bit
[{
systemChat _this;
}, _errorMsg, 1] call CBA_fnc_waitAndExecute;
}, [_errorMsg, CBA_missionTime + 5]] call CBA_fnc_waitUntilAndExecute;
}, [_errorLog, _errorMsg]] call CBA_fnc_waitUntilAndExecute;
};
_clientErrors pushBack _isMissingItems;
} forEach [
[_missingAddonsClient, "client missing"],
[_additionalAddonsClient, "client additional"],
[_olderVersionsClient, "older client"],
[_newerVersionsClient, "newer client"]
];
TRACE_4("",_missingAddonsClient,_additionalAddonsClient,_olderVersionsClient,_newerVersionsClient);
ACE_Version_ClientErrors = _clientErrors;
// Raise event when done
["ace_versioning_clientCheckDone", [+ACE_Version_ClientErrors]] call CBA_fnc_localEvent;
};
// Wait for server to send the servers files and version numbers
if (isNil "ACE_Version_ServerVersions") then {
ACE_Version_ServerVersions addPublicVariableEventHandler _fnc_check;
} else {
call _fnc_check;
};

View File

@ -1,147 +1,141 @@
#include "..\script_component.hpp" #include "..\script_component.hpp"
#include "\a3\ui_f\hpp\defineResincl.inc"
#include "\a3\ui_f\hpp\defineDIKCodes.inc"
/* /*
* Author: commy2, based on BIS_fnc_errorMsg and BIS_fnc_guiMessage by Karel Moricky (BI) * Author: commy2, johnb43, based on BIS_fnc_errorMsg and BIS_fnc_guiMessage by Karel Moricky (BI)
* Stops simulation and opens a textbox with error message. * Opens a textbox with an error message, used for PBO checking.
* *
* Arguments: * Arguments:
* ? * 0: Header <STRING>
* 1: Text <STRING|TEXT>
* *
* Return Value: * Return Value:
* None * None
* *
* Example: * Example:
* call ace_common_fnc_errorMessage * ["[ACE] ERROR", "Test"] call ace_common_fnc_errorMessage
* *
* Public: No * Public: No
*/ */
disableSerialization; // Force stop any loading screens
endLoadingScreen; endLoadingScreen;
// no message without player possible // No message without interface possible
if (!hasInterface) exitWith {}; if (!hasInterface) exitWith {};
// wait for display [{
if (isNull (call BIS_fnc_displayMission)) exitWith { !isNull (call BIS_fnc_displayMission)
[{ }, {
if (isNull (call BIS_fnc_displayMission)) exitWith {}; params ["_textHeader", "_textMessage"];
(_this select 0) call FUNC(errorMessage); disableSerialization;
[_this select 1] call CBA_fnc_removePerFrameHandler;
}, 1, _this] call CBA_fnc_addPerFrameHandler; // Use curator display if present
}; private _curatorDisplay = findDisplay 312;
params ["_textHeader", "_textMessage", ["_onOK", {}], ["_onCancel", {}]]; private _mainDisplay = if (!isNull _curatorDisplay) then {
_curatorDisplay
} else {
call BIS_fnc_displayMission
};
if (_textMessage isEqualType "") then { if (_textMessage isEqualType "") then {
_textMessage = parseText _textMessage; _textMessage = parseText _textMessage;
}; };
ARR_SELECT(_this,4,call BIS_fnc_displayMission) createDisplay "RscDisplayCommonMessagePause"; private _display = _mainDisplay createDisplay "RscDisplayCommonMessagePause";
private _display = uiNamespace getVariable "RscDisplayCommonMessage_display"; if (isNull _display) exitWith {};
private _ctrlRscMessageBox = _display displayCtrl 2351;
private _ctrlBcgCommonTop = _display displayCtrl 235100;
private _ctrlBcgCommon = _display displayCtrl 235101;
private _ctrlText = _display displayCtrl 235102;
private _ctrlBackgroundButtonOK = _display displayCtrl 235103;
private _ctrlBackgroundButtonMiddle = _display displayCtrl 235104;
private _ctrlBackgroundButtonCancel = _display displayCtrl 235105;
private _ctrlButtonOK = _display displayCtrl 235106;
private _ctrlButtonCancel = _display displayCtrl 235107;
_ctrlBcgCommonTop ctrlSetText _textHeader; private _ctrlRscMessageBox = _display displayCtrl 2351;
private _ctrlBcgCommonTop = _display displayCtrl 235100;
private _ctrlBcgCommon = _display displayCtrl 235101;
private _ctrlText = _display displayCtrl 235102;
private _ctrlBackgroundButtonOK = _display displayCtrl 235103;
private _ctrlBackgroundButtonMiddle = _display displayCtrl 235104;
private _ctrlBackgroundButtonCancel = _display displayCtrl 235105;
private _ctrlButtonOK = _display displayCtrl 235106;
private _ctrlButtonCancel = _display displayCtrl 235107;
private _ctrlButtonOKPos = ctrlPosition _ctrlButtonOK; _ctrlBcgCommonTop ctrlSetText _textHeader;
private _ctrlBcgCommonPos = ctrlPosition _ctrlBcgCommon;
private _bottomSpaceY = (_ctrlButtonOKPos select 1) - ((_ctrlBcgCommonPos select 1) + (_ctrlBcgCommonPos select 3));
private _ctrlTextPos = ctrlPosition _ctrlText; private _ctrlButtonOKPos = ctrlPosition _ctrlButtonOK;
private _marginX = (_ctrlTextPos select 0) - (_ctrlBcgCommonPos select 0); private _ctrlBcgCommonPos = ctrlPosition _ctrlBcgCommon;
private _marginY = (_ctrlTextPos select 1) - (_ctrlBcgCommonPos select 1); private _bottomSpaceY = (_ctrlButtonOKPos select 1) - ((_ctrlBcgCommonPos select 1) + (_ctrlBcgCommonPos select 3));
_ctrlText ctrlSetStructuredText _textMessage; private _ctrlTextPos = ctrlPosition _ctrlText;
private _ctrlTextPosH = ctrlTextHeight _ctrlText; private _marginX = (_ctrlTextPos select 0) - (_ctrlBcgCommonPos select 0);
private _marginY = (_ctrlTextPos select 1) - (_ctrlBcgCommonPos select 1);
_ctrlBcgCommon ctrlSetPosition [ _ctrlText ctrlSetStructuredText _textMessage;
_ctrlBcgCommonPos select 0, private _ctrlTextPosH = ctrlTextHeight _ctrlText;
_ctrlBcgCommonPos select 1,
_ctrlBcgCommonPos select 2,
_ctrlTextPosH + _marginY * 2
];
_ctrlBcgCommon ctrlCommit 0;
_ctrlText ctrlSetPosition [ _ctrlBcgCommon ctrlSetPosition [
(_ctrlBcgCommonPos select 0) + _marginX, _ctrlBcgCommonPos select 0,
(_ctrlBcgCommonPos select 1) + _marginY, _ctrlBcgCommonPos select 1,
(_ctrlBcgCommonPos select 2) - _marginX * 2, _ctrlBcgCommonPos select 2,
_ctrlTextPosH _ctrlTextPosH + _marginY * 2
]; ];
_ctrlText ctrlCommit 0; _ctrlBcgCommon ctrlCommit 0;
private _bottomPosY = (_ctrlBcgCommonPos select 1) + _ctrlTextPosH + (_marginY * 2) + _bottomSpaceY; _ctrlText ctrlSetPosition [
(_ctrlBcgCommonPos select 0) + _marginX,
(_ctrlBcgCommonPos select 1) + _marginY,
(_ctrlBcgCommonPos select 2) - _marginX * 2,
_ctrlTextPosH
];
_ctrlText ctrlCommit 0;
{ private _bottomPosY = (_ctrlBcgCommonPos select 1) + _ctrlTextPosH + (_marginY * 2) + _bottomSpaceY;
private _xPos = ctrlPosition _x;
_xPos set [1, _bottomPosY]; {
_x ctrlSetPosition _xPos; private _xPos = ctrlPosition _x;
_x ctrlCommit 0;
} forEach [
_ctrlBackgroundButtonOK,
_ctrlBackgroundButtonMiddle,
_ctrlBackgroundButtonCancel,
_ctrlButtonOK,
_ctrlButtonCancel
];
private _ctrlRscMessageBoxPos = ctrlPosition _ctrlRscMessageBox; _xPos set [1, _bottomPosY];
private _ctrlRscMessageBoxPosH = _bottomPosY + (_ctrlButtonOKPos select 3); _x ctrlSetPosition _xPos;
_x ctrlCommit 0;
} forEach [
_ctrlBackgroundButtonOK,
_ctrlBackgroundButtonMiddle,
_ctrlBackgroundButtonCancel,
_ctrlButtonOK,
_ctrlButtonCancel
];
_ctrlRscMessageBox ctrlSetPosition [ private _ctrlRscMessageBoxPos = ctrlPosition _ctrlRscMessageBox;
0.5 - (_ctrlBcgCommonPos select 2) / 2, private _ctrlRscMessageBoxPosH = _bottomPosY + (_ctrlButtonOKPos select 3);
0.5 - _ctrlRscMessageBoxPosH / 2,
(_ctrlBcgCommonPos select 2) + 0.5,
_ctrlRscMessageBoxPosH
];
_ctrlRscMessageBox ctrlEnable true; _ctrlRscMessageBox ctrlSetPosition [
_ctrlRscMessageBox ctrlCommit 0; 0.5 - (_ctrlBcgCommonPos select 2) / 2,
0.5 - _ctrlRscMessageBoxPosH / 2,
(_ctrlBcgCommonPos select 2) + 0.5,
_ctrlRscMessageBoxPosH
];
if (_onOK isEqualTo {}) then { _ctrlRscMessageBox ctrlEnable true;
_ctrlButtonOK ctrlEnable false; _ctrlRscMessageBox ctrlCommit 0;
_ctrlButtonOK ctrlSetFade 0;
_ctrlButtonOK ctrlSetText ""; // Enable ok button
_ctrlButtonOK ctrlCommit 0;
} else {
_ctrlButtonOK ctrlEnable true; _ctrlButtonOK ctrlEnable true;
_ctrlButtonOK ctrlSetFade 0; _ctrlButtonOK ctrlSetFade 0;
_ctrlButtonOK ctrlSetText localize "STR_DISP_OK"; _ctrlButtonOK ctrlSetText localize "STR_DISP_OK";
_ctrlButtonOK ctrlCommit 0; _ctrlButtonOK ctrlCommit 0;
ctrlSetFocus _ctrlButtonOK; ctrlSetFocus _ctrlButtonOK;
};
if (_onCancel isEqualTo {}) then { // Disable cancel button
_ctrlButtonCancel ctrlEnable false; _ctrlButtonCancel ctrlEnable false;
_ctrlButtonCancel ctrlSetFade 0; _ctrlButtonCancel ctrlSetFade 0;
_ctrlButtonCancel ctrlSetText ""; _ctrlButtonCancel ctrlSetText "";
_ctrlButtonCancel ctrlCommit 0; _ctrlButtonCancel ctrlCommit 0;
} else {
_ctrlButtonCancel ctrlEnable true;
_ctrlButtonCancel ctrlSetFade 0;
_ctrlButtonCancel ctrlSetText localize "STR_DISP_CANCEL";
_ctrlButtonCancel ctrlCommit 0;
ctrlSetFocus _ctrlButtonCancel; _ctrlButtonOK ctrlAddEventHandler ["ButtonClick", {(ctrlParent (_this select 0)) closeDisplay IDC_OK; true}];
};
_ctrlButtonOK ctrlAddEventHandler ["ButtonClick", {(ctrlParent (_this select 0)) closeDisplay 1; true}]; // Intercept all keystrokes except the enter keys
_ctrlButtonCancel ctrlAddEventHandler ["ButtonClick", {(ctrlParent (_this select 0)) closeDisplay 2; true}]; _display displayAddEventHandler ["KeyDown", {!((_this select 1) in [DIK_RETURN, DIK_NUMPADENTER])}];
GVAR(errorOnOK) = _onOK; // Close curator and mission displays (because of the message display, it doesn't quit the mission yet)
GVAR(errorOnCancel) = _onCancel; findDisplay 312 closeDisplay 0;
findDisplay 46 closeDisplay 0;
_display displayAddEventHandler ["Unload", {call ([{}, GVAR(errorOnOK), GVAR(errorOnCancel)] select (_this select 1))}]; }, _this] call CBA_fnc_waitUntilAndExecute;
_display displayAddEventHandler ["KeyDown", {_this select 1 == 1}];

View File

@ -1,160 +0,0 @@
// by commy2
#include "..\script_component.hpp"
private _aceWhitelist = missionNamespace getVariable ["ACE_Version_Whitelist", []];
private _files = CBA_common_addons select {
(_x select [0,3] != "a3_") &&
{_x select [0,4] != "ace_"} &&
{!((toLowerANSI _x) in _aceWhitelist)}
};
private _versions = [];
{
getText (configFile >> "CfgPatches" >> _x >> "version") splitString "." params [["_major", "0"], ["_minor", "0"]];
private _version = parseNumber _major + parseNumber _minor/100;
_versions set [_forEachIndex, _version];
} forEach _files;
if (isServer) then {
ACE_Version_ServerVersions = [_files, _versions];
publicVariable "ACE_Version_ServerVersions";
} else {
ACE_Version_ClientVersions = [_files, _versions];
};
// Begin client version check
if (!isServer) then {
// Wait for server to send the servers files and version numbers
waitUntil {
sleep 1;
!isNil "ACE_Version_ClientVersions" && {!isNil "ACE_Version_ServerVersions"}
};
private _client = profileName;
_files = ACE_Version_ClientVersions select 0;
_versions = ACE_Version_ClientVersions select 1;
private _serverFiles = ACE_Version_ServerVersions select 0;
private _serverVersions = ACE_Version_ServerVersions select 1;
// Compare client and server files and versions
private _missingAddons = [];
private _oldVersionsClient = [];
private _oldVersionsServer = [];
{
private _serverVersion = _serverVersions select _forEachIndex;
private _index = _files find _x;
if (_index == -1) then {
if (_x != "ace_server") then {_missingAddons pushBack _x;};
} else {
private _clientVersion = _versions select _index;
if (_clientVersion < _serverVersion) then {
_oldVersionsClient pushBack [_x, _clientVersion, _serverVersion];
};
if (_clientVersion > _serverVersion) then {
_oldVersionsServer pushBack [_x, _clientVersion, _serverVersion];
};
};
} forEach _serverFiles;
// find client files which the server doesn't have
private _missingAddonsServer = [];
{
private _index = _serverFiles find _x;
if (_index == -1) then {
_missingAddonsServer pushBack _x;
}
} forEach _files;
// display and log error messages
private _fnc_cutComma = {
private _string = _this;
_string = toArray _string;
private _count = count _string;
_string set [_count - 2, toArray "." select 0];
_string set [_count - 1, -1];
_string = _string - [-1];
toString _string;
};
private _missingAddon = false;
if (count _missingAddons > 0) then {
_missingAddon = true;
private _error = format ["[ACE] %1: ERROR client missing addon(s): ", _client];
{
_error = _error + format ["%1, ", _x];
if (_forEachIndex > 9) exitWith {};
} forEach _missingAddons;
_error = _error call _fnc_cutComma;
diag_log text _error;
[QGVAR(systemChatGlobal), _error] call CBA_fnc_globalEvent;
[QGVAR(serverLog), _error] call CBA_fnc_serverEvent;
};
private _missingAddonServer = false;
if (count _missingAddonsServer > 0) then {
_missingAddonServer = true;
private _error = format ["[ACE] %1: ERROR server missing addon(s): ", _client];
{
_error = _error + format ["%1, ", _x];
if (_forEachIndex > 9) exitWith {};
} forEach _missingAddonsServer;
_error = _error call _fnc_cutComma;
diag_log text _error;
[QGVAR(systemChatGlobal), _error] call CBA_fnc_globalEvent;
[QGVAR(serverLog), _error] call CBA_fnc_serverEvent;
};
private _oldVersionClient = false;
if (count _oldVersionsClient > 0) then {
_oldVersionClient = true;
private _error = format ["[ACE] %1: ERROR outdated client addon(s): ", _client];
{
_error = _error + format ["%1 (client: %2, server: %3), ", _x select 0, _x select 1, _x select 2];
if (_forEachIndex > 9) exitWith {};
} forEach _oldVersionsClient;
_error = _error call _fnc_cutComma;
diag_log text _error;
[QGVAR(systemChatGlobal), _error] call CBA_fnc_globalEvent;
[QGVAR(serverLog), _error] call CBA_fnc_serverEvent;
};
private _oldVersionServer = false;
if (count _oldVersionsServer > 0) then {
_oldVersionServer = true;
private _error = format ["[ACE] %1: ERROR outdated server addon(s): ", _client];
{
_error = _error + format ["%1 (client: %2, server: %3), ", _x select 0, _x select 1, _x select 2];
if (_forEachIndex > 9) exitWith {};
} forEach _oldVersionsServer;
_error = _error call _fnc_cutComma;
diag_log text _error;
[QGVAR(systemChatGlobal), _error] call CBA_fnc_globalEvent;
[QGVAR(serverLog), _error] call CBA_fnc_serverEvent;
};
ACE_Version_ClientErrors = [_missingAddon, _missingAddonServer, _oldVersionClient, _oldVersionServer];
};

View File

@ -30,6 +30,8 @@ The vehicle events will also have the following local variables available `_gunn
|`ace_firedPlayerVehicle` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | ACE_player turret fires | |`ace_firedPlayerVehicle` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | ACE_player turret fires |
|`ace_firedPlayerVehicleNonLocal` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | Any other player turret fires | |`ace_firedPlayerVehicleNonLocal` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | Any other player turret fires |
|`ace_firedNonPlayerVehicle` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | AI turret fires | |`ace_firedNonPlayerVehicle` | [_vehicle, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile] | Local | Listen | AI turret fires |
|`ace_versioning_clientCheckDone` | [[_missingAddonsClient, _additionalAddonsClient, _olderVersionsClient, _newerVersionsClient]] | Local | Listen | When PBO checking has finished on a client |
|`ace_versioning_serverCheckDone` | [[_serverFiles, _serverVersions]] | Local | Listen | When PBO checking has finished on the server |
### 2.2 Medical (`ace_medical`) ### 2.2 Medical (`ace_medical`)