Server_SCFramework.SQF 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /*
  2. Server_SCFramework.SQF
  3. Script file that contains all global variables, functions, and eventhandlers related to use of BDC's Server-Client Framework
  4. on the server only
  5. Written by ^bdc April 2017
  6. Modified for automatic headless client offloading Jan 2018
  7. */
  8. // Reset global variables
  9. BDC_SCFramework_HeadlessClientIDs = [];
  10. BDC_SCFramework_HeadlessClientUIDs = [];
  11. BDC_SCFramework_PlayerClientIDs = [];
  12. BDC_SCFramework_HasDisconnectedHC = false; // This global var flags true in the event a headless client disconnects prematurely and prompts the server to re-send all HC's new headless client numbers
  13. // Eventhandlers/Functions
  14. // This eventhandler, tied with SCFramework_PingHeadlessClient, can be used as a two-way acknowledgement
  15. // of the ready status of a particular headless client prior to the offloading of any AI units (setOwner/setGroupOwner)
  16. "SCFramework_HCPingResponseServer" addPublicVariableEventHandler {
  17. _Array = _this select 1;
  18. _Num = _Array select 0;
  19. _Owner = _Array select 1;
  20. SCFramework_PingHCResponse = [_Num,_Owner]; // This array can be referenced on a server-side spawning script to ensure headless client is connected and ready
  21. if (BDC_SCFramework_DetailedLogging) then {
  22. diag_log format["(SCFramework) Response received from headless client %1. Number returned: %2. Global variable array set.",_Owner,_Num];
  23. };
  24. };
  25. // Player client requests its client ID (uses player UID as cross-reference; table is defined and updated in onPlayerConnected)
  26. "SCFramework_PlayerPingResponseServer" addPublicVariableEventHandler {
  27. _UID = _this select 1;
  28. {
  29. _Array = _x;
  30. _ClientID = _Array select 0;
  31. _SCUID = _Array select 1;
  32. if (_SCUID == _UID) then {
  33. SCFramework_PlayerSendClientID = [_UID,_ClientID];
  34. _ClientID publicVariableClient "SCFramework_PlayerSendClientID"; // Player_SCFramework
  35. if (BDC_SCFramework_DetailedLogging) then {
  36. diag_log format["(SCFramework) Ping received from player (UID %1). Sending client ID %2 back.",_UID,_ClientID];
  37. };
  38. };
  39. } forEach BDC_SCFramework_PlayerClientIDs; // onPlayerConnected
  40. };
  41. // Headless client requests its client ID once SCFramework has completed loading on its end
  42. "SCFramework_HCPingRequestClientID" addPublicVariableEventHandler {
  43. _UID = _this select 1;
  44. _Num = 0;
  45. _HCNumber = 1;
  46. {
  47. if (_x == _UID) then {
  48. if (BDC_SCFramework_DetailedLogging) then {
  49. diag_log format["(SCFramework) Client ID requested from headless client with UID %1.",_UID];
  50. };
  51. _ClientID = BDC_SCFramework_HeadlessClientIDs select _Num; // Select from adjacent array
  52. [_ClientID,_HCNumber] spawn Fnc_SCFramework_PingHCClientID;
  53. };
  54. _Num = _Num + 1;
  55. _HCNumber = _HCNumber + 1;
  56. } forEach BDC_SCFramework_HeadlessClientUIDs;
  57. };
  58. // Request for ownership transfer to specific headless client from server (if a particular routine on an HC requests it specifically)
  59. "SCFramework_RequestForGroupOwnership" addPublicVariableEventHandler {
  60. _Array = _this select 1;
  61. _Group = _Array select 0;
  62. _Owner = _Array select 1;
  63. _FoundHCNum = false;
  64. _Counter = 1;
  65. _HCNum = -1;
  66. {
  67. if (_x == _Owner && !_FoundHCNum) then {
  68. _FoundHCNum = true;
  69. _HCNum = _Counter;
  70. };
  71. _Counter = _Counter + 1;
  72. } forEach BDC_SCFramework_HeadlessClientIDs; // onPlayerConnected
  73. if (BDC_SCFramework_DetailedLogging) then {
  74. diag_log format["(SCFramework) Request from headless client #%1 (Client ID %2) for transference of ownership of group %3",_HCNum,_Owner,_Group];
  75. };
  76. _Return = _Group setGroupOwner _Owner;
  77. _Return
  78. };
  79. // This event handler is sent from a player client automatically when a client exits the vehicle from the driver/pilot seat
  80. // so ownership of the vehicle is retained by that player instead of being automatically xferred back to the server
  81. // Also works with grabbing ownership of a vehicle while sling loading - called from Player_SCFramework from GetOutMan EH
  82. "SCFramework_RequestRetainVehOwnership" addPublicVariableEventHandler {
  83. _Array = _this select 1;
  84. _ClientID = _Array select 0;
  85. _Vehicle = _Array select 1;
  86. _Driver = driver _Vehicle;
  87. // Only allow if vehicle is still alive and no driver is in it
  88. if (BDC_SCFramework_ClientRetainVehOwnership && (_Vehicle isKindOf "LandVehicle" || _Vehicle isKindOf "Ship" || _Vehicle isKindOf "Air")) then {
  89. if (BDC_SCFramework_DetailedLogging) then {
  90. diag_log format["(SCFramework) Client ID %1 requesting to retain ownership of vehicle %2 %3",_ClientID,_Vehicle,(typeOf _Vehicle)];
  91. };
  92. if (alive _Vehicle && (isNull _Driver)) then {
  93. if (BDC_SCFramework_DetailedLogging) then {
  94. diag_log format["(SCFramework) Setting ownership of vehicle %1 %2 to Client ID %3.",_Vehicle,(typeOf _Vehicle),_ClientID];
  95. };
  96. _Vehicle setOwner _ClientID;
  97. };
  98. };
  99. };
  100. // Ping for request of FPS information from player client to server and HCs
  101. "SCFramework_ServerFPSRequest" addPublicVariableEventHandler {
  102. _ClientID = _this select 1;
  103. private["_OwnedAI","_CachedAI","_Name","_FPS","_ReturnArray","_OwnedVeh","_CachedVeh"];
  104. // Gather owned AI (cached and not, alive only, no players)
  105. _OwnedAI = 0;
  106. _CachedAI = 0;
  107. {
  108. // Check for AI
  109. if (local _x && alive _x && !isPlayer _x) then {
  110. _OwnedAI = _OwnedAI + 1;
  111. _isAIVCached = false;
  112. _isAIVCached = _x getVariable ["isAIVCached",false]; // Fnc_AIVManager
  113. if (_isAIVCached || !(simulationEnabled _x)) then {
  114. _CachedAI = _CachedAI + 1;
  115. };
  116. };
  117. } forEach allUnits;
  118. _OwnedVeh = 0;
  119. _CachedVeh = 0;
  120. {
  121. if (alive _x && !isPlayer _x) then {
  122. if (_x isKindOf "LandVehicle" || _x isKindOf "Ship" || _x isKindOf "Air") then {
  123. _OwnedVeh = _OwnedVeh + 1;
  124. _isAIVCached = false;
  125. _isAIVCached = _x getVariable ["isAIVCached",false]; // Fnc_AIVManager
  126. if (_isAIVCached || !(simulationEnabled _x)) then {
  127. _CachedVeh = _CachedVeh + 1;
  128. };
  129. };
  130. };
  131. } forEach vehicles;
  132. // Get server FPS and FPSmin
  133. _FPS = round(diag_fps);
  134. _ReturnArray = ["Server",_FPS,_OwnedAI,_CachedAI,_OwnedVeh,_CachedVeh];
  135. SCFramework_ServerFPSResponse = _ReturnArray;
  136. _ClientID publicVariableClient "SCFramework_ServerFPSResponse";
  137. };
  138. // Reset group ownership back to server
  139. "SCFramework_ResetGroupOwnership" addPublicVariableEventHandler {
  140. _Group = _this select 1;
  141. _Group setGroupOwner 2;
  142. if (BDC_SCFramework_DetailedLogging) then {
  143. diag_log format["(SCFramework) SCFramework_ResetGroupOwnership EH called. Resetting ownership of group %1 to server.",_Group];
  144. };
  145. };
  146. // Function written to gather the owner/clientID of a specific headless client based on login order (1, 2, 3, etc)
  147. // Used by server-side mods that are configured to offload AI to a specific headless client by number instead of by name
  148. // How to call:
  149. // _HCClientID = [(Number of headless client logged in)] call Fnc_SCFramework_GetHCClientID;
  150. // _HCClientID = [(Number of hc logged in),"AI Spawner Script Name"] call Fnc_SCFramework_GetHCClientID;
  151. Fnc_SCFramework_GetHCClientID = {
  152. _RequestNum = _this select 0;
  153. _ModuleName = "";
  154. if (count _this > 1) then {
  155. _ModuleName = _this select 1;
  156. };
  157. _ReturnID = -1; // default
  158. if (count BDC_SCFramework_HeadlessClientIDs > 0) then {
  159. _Num = _RequestNum - 1; // onPlayerConnected
  160. _ReturnID = BDC_SCFramework_HeadlessClientIDs select _Num;
  161. if (_ModuleName != "") then {
  162. if (BDC_SCFramework_DetailedLogging) then {
  163. diag_log format["(SCFramework) Request from module %1 for owner/ClientID of headless client #%2 - Returning ClientID %3",_ModuleName,_RequestNum,_ReturnID];
  164. };
  165. } else {
  166. if (BDC_SCFramework_DetailedLogging) then {
  167. diag_log format["(SCFramework) Request for owner/ClientID of headless client #%1 - Returning ClientID %2",_RequestNum,_ReturnID];
  168. };
  169. };
  170. } else {
  171. if (BDC_SCFramework_DetailedLogging) then {
  172. diag_log format["(SCFramework) Function called but currently no headless clients logged in to return a client ID. Returning -1."];
  173. };
  174. };
  175. _ReturnID
  176. };
  177. // Function that will retrieve the ClientID by player UID
  178. // How to call from server:
  179. // _PlayerClientID = ["Player UID string format"] call Fnc_SCFramework_GetPlayerClientID;
  180. Fnc_SCFramework_GetPlayerClientID = {
  181. _UID = _this select 0;
  182. _ReturnID = -1; // Default
  183. if (count BDC_SCFramework_PlayerClientIDs > 0) then { // onPlayerConnected
  184. {
  185. _CUID = _x select 1;
  186. if (_UID == _CUID) then {
  187. _ReturnID = _x select 0;
  188. };
  189. } forEach BDC_SCFramework_PlayerClientIDs;
  190. } else {
  191. _ReturnID = -1;
  192. };
  193. if (BDC_SCFramework_DetailedLogging) then {
  194. diag_log format["(SCFramework) Owner of player UID %1 requested. ClientID returned: %1",_ReturnID];
  195. };
  196. _ReturnID
  197. };
  198. // Set group ownership function (called from server to move a group to a specific clientID/owner)
  199. // How to call from a server-side script:
  200. // [(Group object),(Destination Client ID),"AI Spawning Script Name"] call Fnc_SCFramework_SetGroupOwner;
  201. Fnc_SCFramework_SetGroupOwner = {
  202. _Group = _this select 0;
  203. _ClientID = _this select 1;
  204. _ModuleName = _this select 2; // Module/Script name for logging purposes; ex. "ExileZ","DMS", etc - May also leave blank in quotes if not using SpecificAITable offloading
  205. if (_ModuleName == "") then {
  206. if (BDC_SCFramework_DetailedLogging) then {
  207. diag_log format["(SCFramework) Request from server to send group %1 to Client ID %2.",_Group,_ClientID];
  208. };
  209. } else {
  210. if (BDC_SCFramework_DetailedLogging) then {
  211. diag_log format["(SCFramework) Request from server module %1 to send group %2 to Client ID %3.",_ModuleName,_Group,_ClientID];
  212. };
  213. };
  214. // Move the group and all AI units
  215. _Return = _Group setGroupOwner _ClientID;
  216. if (BDC_SCFramework_DetailedLogging) then {
  217. diag_log format["(SCFramework) Group %1 Module %2 Destination ClientID %3 SetGroupOwner Request response: %4",_Group,_ModuleName,_ClientID,_Return];
  218. };
  219. _Return
  220. };
  221. // Sub-function called by _HCOffloading in gathering number of AI owned by each headless client - Returns array
  222. Fnc_SCFramework_BuildHCAIArray = {
  223. // Build our list of AI's owned by each headless client
  224. _HCAIArray = []; // Nested arrays: [[(HCOwnernNumber),(NumberOfAIOwned)]
  225. {
  226. _OwnedAI = 0; // default
  227. _HCOwner = _x;
  228. _BuildArray = [];
  229. {
  230. if (owner _x == _HCOwner) then {
  231. _OwnedAI = _OwnedAI + 1;
  232. };
  233. } forEach allUnits;
  234. _BuildArray = [_HCOwner,_OwnedAI];
  235. _HCAIArray pushBackUnique _BuildArray;
  236. } forEach BDC_SCFramework_HeadlessClientIDs;
  237. _HCAIArray
  238. };
  239. // Automatic Headless Client offloading sub-function
  240. Fnc_SCFramework_HCOffloading = {
  241. // Start delay
  242. if (BDC_SCFramework_HCOffloading_StartDelay > 0) then {
  243. if (BDC_SCFramework_DetailedLogging) then {
  244. diag_log format["(SCFramework) Start delay configured for Headless Client offloading of %1 seconds...",BDC_SCFramework_HCOffloading_StartDelay];
  245. };
  246. sleep BDC_SCFramework_HCOffloading_StartDelay;
  247. };
  248. // Grab HC client ID's
  249. _HeadlessClientIDs = [];
  250. if (count BDC_SCFramework_HeadlessClientIDs == 0) exitWith {
  251. diag_log format["(SCFramework) No headless clients connected to the server. HC Offloading suspended."];
  252. };
  253. // Start function
  254. diag_log format["(SCFramework) Starting specific headless client offloading."];
  255. while {true} do {
  256. // Check all existing groups to see if we can offload any
  257. _AllGroups = allGroups;
  258. _GroupsToOffload = [];
  259. // Check spawntime on each group first
  260. {
  261. // Check creation time of group - We use this to keep track of group lifetime prior to automatic offloading
  262. _Group = _x;
  263. _SpawnTime = 0;
  264. _SpawnTime = _Group getVariable ["SpawnTime",0];
  265. if (_SpawnTime == 0) then {
  266. _Group setVariable ["SpawnTime",time,true];
  267. };
  268. } forEach _AllGroups;
  269. // Specific AI Table - For groups of AI that've been spawned in using Fnc_SCFramework_CreateGroup via script and configured in SpecificAITable to be offloaded to a specific headless client
  270. if (BDC_SCFramework_HCOffloading_SpecificAITable_Enable) then {
  271. {
  272. _Array = _x;
  273. _ModuleName = _Array select 0;
  274. _VariableName = _Array select 1;
  275. _HCClientNum = _Array select 2;
  276. if (_HCClientNum != 0) then { // If set to 0, then that's server; ignore it
  277. {
  278. _Group = _x;
  279. if (local _Group) then {
  280. _Var = false;
  281. _Var = _Group getVariable [_VariableName,false];
  282. _SpawnTime = time;
  283. _SpawnTime = _Group getVariable ["SpawnTime",0];
  284. _TimeDiff = time - _SpawnTime;
  285. if (_Var && (_TimeDiff >= BDC_SCFramework_HCOffloading_GroupTimerMinimum)) then {
  286. _AllGroups = _AllGroups - [_Group];
  287. // Grab specific HC Client ID from client number passed
  288. _Num = _HCClientNum - 1;
  289. _HCClientID = BDC_SCFramework_HeadlessClientIDs select _Num;
  290. // Add record to array
  291. _AddToOffloadArray = [];
  292. _AddToOffloadArray = [_Group,_HCClientID,_ModuleName];
  293. _GroupsToOffload pushbackUnique _AddToOffloadArray;
  294. };
  295. };
  296. } forEach _AllGroups;
  297. };
  298. } forEach BDC_SCFramework_HCOffloading_SpecificAITable;
  299. };
  300. // Did we collect any specific-configured groups to offload? Let's go
  301. if (count _GroupsToOffload > 0) then {
  302. {
  303. _Array = _x;
  304. _Group = _Array select 0;
  305. _HCClientID = _Array select 1;
  306. _ModuleName = _Array select 2;
  307. [_Group,_HCClientID,_ModuleName] call Fnc_SCFramework_SetGroupOwner;
  308. sleep 0.25; // Brief delay in between each
  309. } forEach _GroupsToOffload;
  310. };
  311. // Automatic offloading, if configured
  312. if (BDC_SCFramework_HCOffloading_AutomaticOffloading_Enable && (count _AllGroups > 0)) then {
  313. // We're using _allGroups as it's already had groups removed per specific AI table config prior to this - So we'll look for groups that we can automatically offload that are leftover
  314. // Gather ownership numbers from each HC, if any connected
  315. if (count BDC_SCFramework_HeadlessClientIDs > 0) then {
  316. _HCAIArray = [] call Fnc_SCFramework_BuildHCAIArray; // Build our array of live AI owned by each headless client
  317. {
  318. _Group = _x;
  319. if (local _Group) then {
  320. _SpawnTime = time;
  321. _SpawnTime = _Group getVariable ["SpawnTime",0];
  322. _TimeDiff = time - _SpawnTime;
  323. if (_TimeDiff >= BDC_SCFramework_HCOffloading_GroupTimerMinimum) then { // Group that server owns that's been around long enough to offload - let's go
  324. _FoundHC = false;
  325. //_HCAIArray = [] call Fnc_SCFramework_BuildHCAIArray; // Build our array of live AI owned by each headless client
  326. _HCCount = 0;
  327. {
  328. _BuildArray = _x;
  329. _HCOwner = _BuildArray select 0;
  330. _OwnedAI = _BuildArray select 1;
  331. if (!_FoundHC && (_OwnedAI < BDC_SCFramework_HCOffloading_AutomaticOffloading_MaxAIPerHeadlessClient)) then {
  332. _FoundHC = true;
  333. _GroupCount = count (units _Group);
  334. _NewOwnedAI = _OwnedAI + _GroupCount;
  335. _BuildArray = [_HCOwner,_NewOwnedAI];
  336. _HCAIArray set [_HCCount,_BuildArray];
  337. [_Group,_HCOwner,""] call Fnc_SCFramework_SetGroupOwner;
  338. sleep 0.25; // Brief delay in between each
  339. };
  340. _HCCount = _HCCount + 1;
  341. } forEach _HCAIArray;
  342. };
  343. };
  344. } forEach _allGroups;
  345. };
  346. };
  347. sleep BDC_SCFramework_HCOffloading_Frequency;
  348. };
  349. };
  350. // Headless Client automated AI offloading
  351. if (BDC_SCFramework_HCOffloading_Enable) then {
  352. [] spawn Fnc_SCFramework_HCOffloading;
  353. };
  354. // Performance/AI Ownership logging
  355. BDC_SCFramework_Logging = {
  356. _LastLog = 0;
  357. while {true} do {
  358. _TimeDiff = time - _LastLog;
  359. if (_TimeDiff >= BDC_SCFramework_LoggingFreq) then {
  360. _LastLog = time;
  361. _StartLogTime = time;
  362. _FPS = diag_FPS;
  363. _FPSMin = diag_fpsMin;
  364. _CachedAI = 0;
  365. _OwnedUnits = 0;
  366. {
  367. if (local _x && !isPlayer _x) then {
  368. _OwnedUnits = _OwnedUnits + 1;
  369. if !(simulationEnabled _x) then {
  370. _CachedAI = _CachedAI + 1;
  371. };
  372. };
  373. } forEach allUnits;
  374. _ActiveAI = _OwnedUnits - _CachedAI;
  375. if (!isServer) then {
  376. diag_log format["(SCFramework) Headless Client #%5 - Current FPS: %1 | FPS Min: %2 | Locally Owned AI: %3 | Cached AI: %4 | Active AI: %6",_FPS,_FPSMin,_OwnedUnits,_CachedAI,SCFramework_HCNumber,_ActiveAI];
  377. } else {
  378. diag_log format["(SCFramework) Server - Current FPS: %1 | FPS Min: %2 | Locally Owned AI: %3 | Cached AI: %4 | Active AI: %5",_FPS,_FPSMin,_OwnedUnits,_CachedAI,_ActiveAI];
  379. diag_log format["(SCFramework) Connected Headless Clients: %1",(count BDC_SCFramework_HeadlessClientIDs)];
  380. };
  381. };
  382. sleep 30;
  383. };
  384. };
  385. // Ping headless client to send it its client ID after connection (called from onPlayerConnected below)
  386. Fnc_SCFramework_PingHCClientID = {
  387. _owner = _this select 0;
  388. _count = _this select 1;
  389. if (BDC_SCFramework_DetailedLogging) then {
  390. diag_log format["(SCFramework) Sending ping to headless client owner %1 with HCNumber %2.",_owner,_count];
  391. };
  392. SCFramework_HCSendClientID = [1,_owner,_count];
  393. _owner publicVariableClient "SCFramework_HCSendClientID";
  394. };
  395. // OnPlayerConnected (or headless client) - used to dole out client IDs
  396. Fnc_SCFramework_onPlayerConnected = {
  397. // Passed Args
  398. _UID = _this select 0;
  399. _name = _this select 1;
  400. _owner = _this select 2;
  401. // Determine if headless client logging in
  402. _isHC = false;
  403. _NewStr = _UID select [0,2]; // Grab first 2 letters of the UID; all HC's logging in use 'HC' and then a number (drawn from PID of arma.exe process in TaskMan)
  404. if (_NewStr == "HC") then {
  405. _isHC = true;
  406. BDC_SCFramework_HeadlessClientIDs pushBackUnique _owner;
  407. BDC_SCFramework_HeadlessClientUIDs pushBackUnique _UID; // This array is used to cross-reference ping to client ID when HC initially pings server requesting its client ID - ^bdc
  408. diag_log format["(SCFramework) onPlayerConnected: Headless Client %1 %2 with ClientID %3 connected.",_UID,_name,_owner];
  409. diag_log format["(SCFramework) Current number of headless clients now connected: %1",(count BDC_SCFramework_HeadlessClientIDs)];
  410. if (!BDC_SCFramework_HasDisconnectedHC) then {
  411. [_owner,(count BDC_SCFramework_HeadlessClientIDs)] spawn Fnc_SCFramework_PingHCClientID;
  412. } else {
  413. BDC_SCFramework_HasDisconnectedHC = false;
  414. diag_log format["(SCFramework) Crashed/Disconnected Headless Client re-connecting. Re-sending ID's to all HC's."];
  415. _Count = 1;
  416. {
  417. [_x,_Count] spawn Fnc_SCFramework_PingHCClientID;
  418. _Count = _Count + 1;
  419. } forEach BDC_SCFramework_HeadlessClientIDs;
  420. };
  421. } else { // Keep track of player client id's in a separate array
  422. if (_name != "__SERVER__") then {
  423. if (_UID in BDC_SCFramework_ServerFPSReport_AdminUIDList) then {
  424. diag_log format["(SCFramework) onPlayerConnected: ADMIN %1 (%2) has connected.",_name,_UID];
  425. } else {
  426. diag_log format["(SCFramework) onPlayerConnected: Player %1 (%2) has connected.",_name,_UID];
  427. };
  428. _SCArray = [_owner,_UID]; // Two element array containing client ID (from owner) and the player UID (so as to remove possibility of difference with player names)
  429. BDC_SCFramework_PlayerClientIDs pushBackUnique _SCArray;
  430. };
  431. };
  432. };
  433. // OnPlayerDisconnected (or HC) - We are using this only to track the disconnection of a headless client
  434. Fnc_SCFramework_onPlayerDisconnected = {
  435. // Passed Args
  436. _UID = _this select 0;
  437. _name = _this select 1;
  438. _owner = _this select 2;
  439. // Determine if headless client logging in
  440. _isHC = false;
  441. _NewStr = _UID select [0,2];
  442. if (_NewStr == "HC") then {
  443. BDC_SCFramework_HeadlessClientIDs = [BDC_SCFramework_HeadlessClientIDs,_owner] call Fnc_SCFramework_DeleteArrayElement; // Functions_SCFramework
  444. BDC_SCFramework_HeadlessClientUIDs = [BDC_SCFramework_HeadlessClientUIDs,_UID] call Fnc_SCFramework_DeleteArrayElement;
  445. diag_log format["(SCFramework) Headless Client %1 %2 with ClientID %3 has either crashed or disconnected in an unknown manner. Removing from global arrays.",_UID,_name,_owner];
  446. diag_log format["(SCFramework) Current number of headless clients now connected: %1",(count BDC_SCFramework_HeadlessClientIDs)];
  447. BDC_SCFramework_HasDisconnectedHC = true; // Will flag false if another HC logs in; reason being is we want to re-shuffle all the headless client ID's after one crashes and re-connects successfully
  448. } else {
  449. diag_log format["(SCFramework) Player %1 (%2) disconnected.",_name,_UID];
  450. _TArray = [_owner,_UID];
  451. BDC_SCFramework_PlayerClientIDs = BDC_SCFramework_PlayerClientIDs - [_TArray];
  452. };
  453. };
  454. // Group manager (empty group deletion) function
  455. Fnc_SCFramework_GroupManager = {
  456. sleep BDC_SCFramework_HCOffloading_StartDelay; // Sleep before starting - we wait until the HCOffloading start delay has expired
  457. diag_log format["(SCFramework) Server - Starting empty group deletion manager."];
  458. while {true} do {
  459. _Ctr = 0;
  460. _CountBefore = count allGroups;
  461. {
  462. if (count units _x == 0) then {
  463. deleteGroup _x; // Delete empty group
  464. };
  465. } forEach allGroups;
  466. _CountAfter = count allGroups;
  467. _Ctr = (_CountBefore - _CountAfter);
  468. if (_Ctr > 0) then {
  469. diag_log format["(SCFramework) Group Manager: Deleted %1 empty groups. Groups prior: %2 | Active groups remaining: %3",_Ctr,_CountBefore,_CountAfter];
  470. };
  471. sleep 300; // Run once every 5 minutes
  472. };
  473. };
  474. // Performance logging routine
  475. _LogRoutine = {
  476. if (BDC_SCFramework_LoggingFreq > 0) then {
  477. if (BDC_SCFramework_LoggingDelay > 0) then {
  478. diag_log format["(SCFramework) Delaying start of performance and AI ownership automated logging for %1 seconds.",BDC_SCFramework_LoggingDelay];
  479. sleep BDC_SCFramework_LoggingDelay;
  480. };
  481. diag_log format["(SCFramework) Starting performance and AI ownership logging for server every %1 seconds.",BDC_SCFramework_LoggingFreq];
  482. [] spawn BDC_SCFramework_Logging;
  483. };
  484. };
  485. // Stacked EH for onPlayerConnected and onPlayerDisconnected - track when clients connect and disconnect/crash
  486. ["SCFrameworkOnPlayerConnected", "onPlayerConnected", { [_uid, _name, _owner] call Fnc_SCFramework_onPlayerConnected; }] call BIS_fnc_addStackedEventHandler;
  487. ["SCFrameworkOnPlayerDisconnected", "onPlayerDisconnected", { [_uid, _name, _owner] call Fnc_SCFramework_onPlayerDisconnected; }] call BIS_fnc_addStackedEventHandler;
  488. // Start logging routine, if enabled
  489. [] spawn _LogRoutine;
  490. // Start group manager
  491. if (BDC_SCFramework_GroupManagerEnable) then {
  492. [] spawn Fnc_SCFramework_GroupManager;
  493. };
  494. diag_log format["(SCFramework) Server: Headless and player client ID's arrays initialized. SCFramework server-side eventhandlers and functions loaded."];