missionserver.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. //! int time of the logout end
  2. //: string uid of the player
  3. typedef Param2<int, string> LogoutInfo;
  4. class MissionServer extends MissionBase
  5. {
  6. ref array<Man> m_Players;
  7. ref array<ref CorpseData> m_DeadPlayersArray;
  8. ref map<PlayerBase, ref LogoutInfo> m_LogoutPlayers;
  9. ref map<PlayerBase, ref LogoutInfo> m_NewLogoutPlayers;
  10. ref RainProcurementHandler m_RainProcHandler;
  11. const int SCHEDULER_PLAYERS_PER_TICK = 5;
  12. int m_currentPlayer;
  13. int m_RespawnMode;
  14. // -----------------------
  15. // ARTILLERY SOUNDS SETUP
  16. // -----------------------
  17. private float m_ArtyBarrageTimer = 0; // This is not to be edited in Init.c this is just to increment time
  18. // Variables to be modified in Init.c
  19. protected bool m_PlayArty = false; // Toggle if Off map artillery sounds are played
  20. protected float m_ArtyDelay = 0; // Set how much time there is between two barrages (in seconds)
  21. protected int m_MinSimultaneousStrikes = 0; // The MIN of simultaneous shots on the map (Will be clamped between 1 and max shots)
  22. protected int m_MaxSimultaneousStrikes = 0; // The MAX of simultaneous shots on the map (Will be clamped between 1 and max amount of coords)
  23. protected ref array<vector> m_FiringPos; // Where we should fire from. On Init set the relevant data
  24. //All Chernarus firing coordinates
  25. protected const ref array<vector> CHERNARUS_STRIKE_POS =
  26. {
  27. "-500.00 165.00 5231.69",
  28. "-500.00 300.00 9934.41",
  29. "10406.86 192.00 15860.00",
  30. "4811.75 370.00 15860.00",
  31. "-500.00 453.00 15860.00"
  32. };
  33. //All livonia firing coordinates
  34. protected const ref array<vector> LIVONIA_STRIKE_POS =
  35. {
  36. "7440.00 417.00 -500.00",
  37. "-500.00 276.00 5473.00",
  38. "-500.00 265.00 9852.00",
  39. "4953.00 240.00 13300.00",
  40. "9620.00 188.00 13300.00",
  41. "13300.00 204.00 10322.00",
  42. "13300.00 288.00 6204.00",
  43. "13300.00 296.00 -500.00"
  44. };
  45. // -----------------------
  46. // END OF ARTILLERY SETUP
  47. // -----------------------
  48. PlayerBase m_player;
  49. MissionBase m_mission;
  50. void MissionServer()
  51. {
  52. GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.UpdatePlayersStats, 30000, true);
  53. m_DeadPlayersArray = new array<ref CorpseData>;
  54. UpdatePlayersStats();
  55. m_Players = new array<Man>;
  56. m_LogoutPlayers = new map<PlayerBase, ref LogoutInfo>;
  57. m_NewLogoutPlayers = new map<PlayerBase, ref LogoutInfo>;
  58. m_RainProcHandler = new RainProcurementHandler(this);
  59. m_ActiveRefresherLocations = new array<vector>();
  60. }
  61. void ~MissionServer()
  62. {
  63. GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).Remove(this.UpdatePlayersStats);
  64. }
  65. override void OnInit()
  66. {
  67. super.OnInit();
  68. CfgGameplayHandler.LoadData();
  69. PlayerSpawnHandler.LoadData();
  70. CfgPlayerRestrictedAreaHandler.LoadData();
  71. UndergroundAreaLoader.SpawnAllTriggerCarriers();
  72. //Either pass consts in Init.c or insert all desired coords (or do both ;))
  73. m_FiringPos = new array<vector>();
  74. }
  75. override void OnMissionStart()
  76. {
  77. super.OnMissionStart();
  78. // We will load the Effect areas on Default mission start
  79. EffectAreaLoader.CreateZones();
  80. }
  81. override void OnUpdate(float timeslice)
  82. {
  83. UpdateDummyScheduler();
  84. TickScheduler(timeslice);
  85. UpdateLogoutPlayers();
  86. m_WorldData.UpdateBaseEnvTemperature(timeslice); // re-calculate base enviro temperature
  87. m_RainProcHandler.Update(timeslice);
  88. RandomArtillery(timeslice);
  89. super.OnUpdate(timeslice);
  90. }
  91. override void OnGameplayDataHandlerLoad()
  92. {
  93. m_RespawnMode = CfgGameplayHandler.GetDisableRespawnDialog();
  94. GetGame().SetDebugMonitorEnabled(GetGame().ServerConfigGetInt("enableDebugMonitor"));
  95. InitialiseWorldData();
  96. }
  97. void RandomArtillery(float deltaTime)
  98. {
  99. // ARTY barrage
  100. if (m_PlayArty)
  101. {
  102. // We only perform timer checks and increments if we enabled the artillery barrage
  103. if (m_ArtyBarrageTimer > m_ArtyDelay)
  104. {
  105. //We clamp to guarantee 1 and never have multiple shots on same pos, even in case of entry error
  106. m_MaxSimultaneousStrikes = Math.Clamp(m_MaxSimultaneousStrikes, 1, m_FiringPos.Count());
  107. m_MinSimultaneousStrikes = Math.Clamp(m_MinSimultaneousStrikes, 1, m_MaxSimultaneousStrikes);
  108. // Variables to be used in this scope
  109. int randPos; // Select random position
  110. Param1<vector> pos; // The value to be sent through RPC
  111. array<ref Param> params; // The RPC params
  112. if (m_MaxSimultaneousStrikes == 1)
  113. {
  114. // We only have one set of coordinates to send
  115. randPos = Math.RandomIntInclusive(0, m_FiringPos.Count() - 1);
  116. pos = new Param1<vector>(m_FiringPos[randPos]);
  117. params = new array<ref Param>;
  118. params.Insert(pos);
  119. GetGame().RPC(null, ERPCs.RPC_SOUND_ARTILLERY, params, true);
  120. }
  121. else
  122. {
  123. //We will do some extra steps to
  124. /*
  125. 1. Send multiple coords (Send one RPC per coord set)
  126. 2. Ensure we don't have duplicates
  127. */
  128. array<int> usedIndices = new array<int>; // Will store all previusly fired upon indices
  129. // We determine how many positions fire between MIN and MAX
  130. int randFireNb = Math.RandomIntInclusive(m_MinSimultaneousStrikes, m_MaxSimultaneousStrikes);
  131. for (int i = 0; i < randFireNb; i++)
  132. {
  133. randPos = Math.RandomIntInclusive(0, m_FiringPos.Count() - 1);
  134. if (usedIndices.Count() <= 0 || usedIndices.Find(randPos) < 0) //We do not find the index or array is empty
  135. {
  136. // We prepare to send the message
  137. pos = new Param1<vector>(m_FiringPos[randPos]);
  138. params = new array<ref Param>;
  139. // We send the message with this set of coords
  140. params.Insert(pos);
  141. GetGame().RPC(null, ERPCs.RPC_SOUND_ARTILLERY, params, true);
  142. // We store the last used value
  143. usedIndices.Insert(randPos);
  144. }
  145. }
  146. }
  147. // Reset timer for new loop
  148. m_ArtyBarrageTimer = 0.0;
  149. }
  150. m_ArtyBarrageTimer += deltaTime;
  151. }
  152. }
  153. override bool IsServer()
  154. {
  155. return true;
  156. }
  157. override bool IsPlayerDisconnecting(Man player)
  158. {
  159. return (m_LogoutPlayers && m_LogoutPlayers.Contains(PlayerBase.Cast(player))) || (m_NewLogoutPlayers && m_NewLogoutPlayers.Contains(PlayerBase.Cast(player)));
  160. }
  161. void UpdatePlayersStats()
  162. {
  163. PluginLifespan moduleLifespan;
  164. Class.CastTo(moduleLifespan, GetPlugin(PluginLifespan));
  165. array<Man> players = new array<Man>();
  166. GetGame().GetPlayers(players);
  167. foreach (Man man : players)
  168. {
  169. PlayerBase player;
  170. if (Class.CastTo(player, man))
  171. {
  172. player.StatUpdateByTime(AnalyticsManagerServer.STAT_PLAYTIME);
  173. player.StatUpdateByPosition(AnalyticsManagerServer.STAT_DISTANCE);
  174. moduleLifespan.UpdateLifespan(player);
  175. }
  176. }
  177. UpdateCorpseStatesServer();
  178. }
  179. protected void AddNewPlayerLogout(PlayerBase player, notnull LogoutInfo info)
  180. {
  181. m_LogoutPlayers.Insert(player, info);
  182. m_NewLogoutPlayers.Remove(player);
  183. }
  184. // check if logout finished for some players
  185. void UpdateLogoutPlayers()
  186. {
  187. for (int i = 0; i < m_LogoutPlayers.Count();)
  188. {
  189. LogoutInfo info = m_LogoutPlayers.GetElement(i);
  190. if (GetGame().GetTime() >= info.param1)
  191. {
  192. PlayerIdentity identity;
  193. PlayerBase player = m_LogoutPlayers.GetKey(i);
  194. if (player)
  195. {
  196. identity = player.GetIdentity();
  197. m_LogoutPlayers.Remove(player);
  198. }
  199. else
  200. {
  201. m_LogoutPlayers.RemoveElement(i);
  202. }
  203. // disable reconnecting to old char
  204. // GetGame().RemoveFromReconnectCache(info.param2);
  205. PlayerDisconnected(player, identity, info.param2);
  206. }
  207. else
  208. {
  209. ++i;
  210. }
  211. }
  212. }
  213. override void OnEvent(EventType eventTypeId, Param params)
  214. {
  215. PlayerIdentity identity;
  216. PlayerBase player;
  217. int counter = 0;
  218. switch (eventTypeId)
  219. {
  220. case ClientPrepareEventTypeID:
  221. ClientPrepareEventParams clientPrepareParams;
  222. Class.CastTo(clientPrepareParams, params);
  223. CfgGameplayHandler.SyncDataSendEx(clientPrepareParams.param1);
  224. UndergroundAreaLoader.SyncDataSend(clientPrepareParams.param1);
  225. CfgPlayerRestrictedAreaHandler.SyncDataSend(clientPrepareParams.param1);
  226. OnClientPrepareEvent(clientPrepareParams.param1, clientPrepareParams.param2, clientPrepareParams.param3, clientPrepareParams.param4, clientPrepareParams.param5);
  227. break;
  228. case ClientNewEventTypeID:
  229. ClientNewEventParams newParams;
  230. Class.CastTo(newParams, params);
  231. player = OnClientNewEvent(newParams.param1, newParams.param2, newParams.param3);
  232. if (!player)
  233. {
  234. Debug.Log("ClientNewEvent: Player is empty");
  235. return;
  236. }
  237. identity = newParams.param1;
  238. InvokeOnConnect(player,identity);
  239. SyncEvents.SendPlayerList();
  240. ControlPersonalLight(player);
  241. SyncGlobalLighting(player);
  242. break;
  243. case ClientReadyEventTypeID:
  244. ClientReadyEventParams readyParams;
  245. Class.CastTo(readyParams, params);
  246. identity = readyParams.param1;
  247. Class.CastTo(player, readyParams.param2);
  248. if (!player)
  249. {
  250. Debug.Log("ClientReadyEvent: Player is empty");
  251. return;
  252. }
  253. OnClientReadyEvent(identity, player);
  254. InvokeOnConnect(player, identity);
  255. // Send list of players at all clients
  256. SyncEvents.SendPlayerList();
  257. ControlPersonalLight(player);
  258. SyncGlobalLighting(player);
  259. break;
  260. case ClientRespawnEventTypeID:
  261. ClientRespawnEventParams respawnParams;
  262. Class.CastTo(respawnParams, params);
  263. identity = respawnParams.param1;
  264. Class.CastTo(player, respawnParams.param2);
  265. if (!player)
  266. {
  267. Debug.Log("ClientRespawnEvent: Player is empty");
  268. return;
  269. }
  270. OnClientRespawnEvent(identity, player);
  271. break;
  272. case ClientReconnectEventTypeID:
  273. ClientReconnectEventParams reconnectParams;
  274. Class.CastTo(reconnectParams, params);
  275. identity = reconnectParams.param1;
  276. Class.CastTo(player, reconnectParams.param2);
  277. if (!player)
  278. {
  279. Debug.Log("ClientReconnectEvent: Player is empty");
  280. return;
  281. }
  282. OnClientReconnectEvent(identity, player);
  283. break;
  284. case ClientDisconnectedEventTypeID:
  285. ClientDisconnectedEventParams discoParams;
  286. Class.CastTo(discoParams, params);
  287. identity = discoParams.param1;
  288. Class.CastTo(player, discoParams.param2);
  289. int logoutTime = discoParams.param3;
  290. bool authFailed = discoParams.param4;
  291. if (!player)
  292. {
  293. Debug.Log("ClientDisconnectenEvent: Player is empty");
  294. return;
  295. }
  296. OnClientDisconnectedEvent(identity, player, logoutTime, authFailed);
  297. break;
  298. case LogoutCancelEventTypeID:
  299. LogoutCancelEventParams logoutCancelParams;
  300. Class.CastTo(logoutCancelParams, params);
  301. Class.CastTo(player, logoutCancelParams.param1);
  302. identity = player.GetIdentity();
  303. if (identity)
  304. {
  305. // disable reconnecting to old char
  306. // GetGame().RemoveFromReconnectCache(identity.GetId());
  307. Print("[Logout]: Player " + identity.GetId() + " cancelled");
  308. }
  309. else
  310. {
  311. Print("[Logout]: Player cancelled");
  312. }
  313. m_LogoutPlayers.Remove(player);
  314. m_NewLogoutPlayers.Remove(player);
  315. break;
  316. }
  317. }
  318. void InvokeOnConnect(PlayerBase player, PlayerIdentity identity)
  319. {
  320. Debug.Log("InvokeOnConnect:"+this.ToString(),"Connect");
  321. if (player)
  322. player.OnConnect();
  323. }
  324. void InvokeOnDisconnect(PlayerBase player)
  325. {
  326. Debug.Log("InvokeOnDisconnect:"+this.ToString(),"Connect");
  327. if (player)
  328. player.OnDisconnect();
  329. }
  330. void OnClientPrepareEvent(PlayerIdentity identity, out bool useDB, out vector pos, out float yaw, out int preloadTimeout)
  331. {
  332. if (GetHive())
  333. {
  334. // use character from database
  335. useDB = true;
  336. }
  337. else
  338. {
  339. // use following data without database
  340. useDB = false;
  341. pos = "1189.3 0.0 5392.48";
  342. yaw = 0;
  343. }
  344. }
  345. // Enables/Disables personal light on the given player.
  346. void ControlPersonalLight(PlayerBase player)
  347. {
  348. if (player)
  349. {
  350. bool is_personal_light = ! GetGame().ServerConfigGetInt("disablePersonalLight");
  351. Param1<bool> personal_light_toggle = new Param1<bool>(is_personal_light);
  352. GetGame().RPCSingleParam(player, ERPCs.RPC_TOGGLE_PERSONAL_LIGHT, personal_light_toggle, true, player.GetIdentity());
  353. }
  354. else
  355. {
  356. Error("Error! Player was not initialized at the right time. Thus cannot send RPC command to enable or disable personal light!");
  357. }
  358. }
  359. // syncs global lighting setup from the server (lightingConfig server config parameter)
  360. void SyncGlobalLighting(PlayerBase player)
  361. {
  362. if (player)
  363. {
  364. int lightingID = GetGame().ServerConfigGetInt("lightingConfig");
  365. Param1<int> lightID = new Param1<int>(lightingID);
  366. GetGame().RPCSingleParam(player, ERPCs.RPC_SEND_LIGHTING_SETUP, lightID, true, player.GetIdentity());
  367. }
  368. }
  369. //! returns whether received data is valid, ctx can be filled on client in StoreLoginData()
  370. bool ProcessLoginData(ParamsReadContext ctx)
  371. {
  372. //creates temporary server-side structure for handling default character spawn
  373. return GetGame().GetMenuDefaultCharacterData(false).DeserializeCharacterData(ctx);
  374. }
  375. //
  376. PlayerBase CreateCharacter(PlayerIdentity identity, vector pos, ParamsReadContext ctx, string characterName)
  377. {
  378. Entity playerEnt;
  379. playerEnt = GetGame().CreatePlayer(identity, characterName, pos, 0, "NONE");//Creates random player
  380. Class.CastTo(m_player, playerEnt);
  381. GetGame().SelectPlayer(identity, m_player);
  382. return m_player;
  383. }
  384. //! Spawns character equip from received data. Checks validity against config, randomizes if invalid value and config array not empty.
  385. void EquipCharacter(MenuDefaultCharacterData char_data)
  386. {
  387. int slot_ID;
  388. string attachment_type;
  389. for (int i = 0; i < DefaultCharacterCreationMethods.GetAttachmentSlotsArray().Count(); i++)
  390. {
  391. slot_ID = DefaultCharacterCreationMethods.GetAttachmentSlotsArray().Get(i);
  392. attachment_type = "";
  393. if (m_RespawnMode != GameConstants.RESPAWN_MODE_CUSTOM || !char_data.GetAttachmentMap().Find(slot_ID,attachment_type) || !VerifyAttachmentType(slot_ID,attachment_type)) //todo insert verification fn here
  394. {
  395. //randomize
  396. if (DefaultCharacterCreationMethods.GetConfigArrayCountFromSlotID(slot_ID) > 0)
  397. {
  398. attachment_type = DefaultCharacterCreationMethods.GetConfigAttachmentTypes(slot_ID).GetRandomElement();
  399. }
  400. else //undefined, moving on
  401. continue;
  402. }
  403. if (attachment_type != "")
  404. {
  405. m_player.GetInventory().CreateAttachmentEx(attachment_type,slot_ID);
  406. }
  407. }
  408. StartingEquipSetup(m_player, true);
  409. }
  410. //! can be overriden to manually set up starting equip. 'clothesChosen' is legacy parameter, does nothing.
  411. void StartingEquipSetup(PlayerBase player, bool clothesChosen)
  412. {
  413. }
  414. bool VerifyAttachmentType(int slot_ID, string attachment_type)
  415. {
  416. return DefaultCharacterCreationMethods.GetConfigAttachmentTypes(slot_ID).Find(attachment_type) > -1;
  417. }
  418. PlayerBase OnClientNewEvent(PlayerIdentity identity, vector pos, ParamsReadContext ctx)
  419. {
  420. string characterType = GetGame().CreateRandomPlayer();
  421. bool generateRandomEquip = false;
  422. // get login data for new character
  423. if (ProcessLoginData(ctx) && (m_RespawnMode == GameConstants.RESPAWN_MODE_CUSTOM) && !GetGame().GetMenuDefaultCharacterData(false).IsRandomCharacterForced())
  424. {
  425. if (GetGame().ListAvailableCharacters().Find(GetGame().GetMenuDefaultCharacterData().GetCharacterType()) > -1)
  426. characterType = GetGame().GetMenuDefaultCharacterData().GetCharacterType();
  427. }
  428. else
  429. {
  430. generateRandomEquip = true;
  431. }
  432. if (PlayerSpawnHandler.IsInitialized())
  433. {
  434. PlayerSpawnPreset presetData = PlayerSpawnHandler.GetRandomCharacterPreset();
  435. if (presetData && presetData.IsValid())
  436. {
  437. string presetCharType = presetData.GetRandomCharacterType();
  438. if (presetCharType == string.Empty)
  439. presetCharType = characterType;
  440. if (CreateCharacter(identity, pos, ctx, presetCharType) != null)
  441. {
  442. PlayerSpawnHandler.ProcessEquipmentData(m_player,presetData);
  443. return m_player;
  444. }
  445. else
  446. {
  447. ErrorEx("Failed to create character from type: " + presetCharType + ", using default spawning method");
  448. }
  449. }
  450. else
  451. {
  452. ErrorEx("Failed to load PlayerSpawnPreset data properly, using default spawning method");
  453. }
  454. }
  455. if (CreateCharacter(identity, pos, ctx, characterType))
  456. {
  457. if (generateRandomEquip)
  458. GetGame().GetMenuDefaultCharacterData().GenerateRandomEquip();
  459. EquipCharacter(GetGame().GetMenuDefaultCharacterData());
  460. }
  461. return m_player;
  462. }
  463. void OnClientReadyEvent(PlayerIdentity identity, PlayerBase player)
  464. {
  465. GetGame().SelectPlayer(identity, player);
  466. #ifdef DIAG_DEVELOPER
  467. if (FeatureTimeAccel.m_CurrentTimeAccel)
  468. {
  469. GetGame().RPCSingleParam(player, ERPCs.DIAG_TIMEACCEL_CLIENT_SYNC, FeatureTimeAccel.m_CurrentTimeAccel, true, identity);
  470. }
  471. #endif
  472. }
  473. void OnClientRespawnEvent(PlayerIdentity identity, PlayerBase player)
  474. {
  475. if (player)
  476. {
  477. if (player.IsUnconscious() || player.IsRestrained())
  478. {
  479. PluginAdminLog adm = PluginAdminLog.Cast(GetPlugin(PluginAdminLog));
  480. adm.PlayerKilledByRespawn(player);
  481. // kill character
  482. player.SetHealth("", "", 0.0);
  483. }
  484. }
  485. #ifdef DIAG_DEVELOPER
  486. if (FeatureTimeAccel.m_CurrentTimeAccel)
  487. {
  488. GetGame().RPCSingleParam(player, ERPCs.DIAG_TIMEACCEL_CLIENT_SYNC, FeatureTimeAccel.m_CurrentTimeAccel, true, identity);
  489. }
  490. #endif
  491. }
  492. void OnClientReconnectEvent(PlayerIdentity identity, PlayerBase player)
  493. {
  494. if (player)
  495. {
  496. player.OnReconnect();
  497. }
  498. }
  499. void OnClientDisconnectedEvent(PlayerIdentity identity, PlayerBase player, int logoutTime, bool authFailed)
  500. {
  501. bool disconnectNow = true;
  502. // TODO: get out of vehicle
  503. // using database and no saving if authorization failed
  504. if (GetHive() && !authFailed)
  505. {
  506. if (player.IsAlive())
  507. {
  508. if (!m_LogoutPlayers.Contains(player) && !m_NewLogoutPlayers.Contains(player))
  509. {
  510. Print("[Logout]: New player " + identity.GetId() + " with logout time " + logoutTime.ToString());
  511. // send statistics to client
  512. player.StatSyncToClient();
  513. // inform client about logout time
  514. GetGame().SendLogoutTime(player, logoutTime);
  515. // wait for some time before logout and save
  516. LogoutInfo params = new LogoutInfo(GetGame().GetTime() + logoutTime * 1000, identity.GetId());
  517. m_NewLogoutPlayers.Insert(player, params);
  518. GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(AddNewPlayerLogout, 0, false, player, params);
  519. // allow reconnecting to old char only if not in cars, od ladders etc. as they cannot be properly synchronized for reconnect
  520. //if (!player.GetCommand_Vehicle() && !player.GetCommand_Ladder())
  521. //{
  522. // GetGame().AddToReconnectCache(identity);
  523. //}
  524. // wait until logout timer runs out
  525. disconnectNow = false;
  526. }
  527. return;
  528. }
  529. }
  530. if (disconnectNow)
  531. {
  532. Print("[Logout]: New player " + identity.GetId() + " with instant logout");
  533. // inform client about instant logout
  534. GetGame().SendLogoutTime(player, 0);
  535. PlayerDisconnected(player, identity, identity.GetId());
  536. }
  537. }
  538. void PlayerDisconnected(PlayerBase player, PlayerIdentity identity, string uid)
  539. {
  540. // Note: At this point, identity can be already deleted
  541. if (!player)
  542. {
  543. Print("[Logout]: Skipping player " + uid + ", already removed");
  544. return;
  545. }
  546. // disable reconnecting to old char
  547. //GetGame().RemoveFromReconnectCache(uid);
  548. // now player can't cancel logout anymore, so call everything needed upon disconnect
  549. InvokeOnDisconnect(player);
  550. Print("[Logout]: Player " + uid + " finished");
  551. if (GetHive())
  552. {
  553. // save player
  554. player.Save();
  555. // unlock player in DB
  556. GetHive().CharacterExit(player);
  557. }
  558. // handle player's existing char in the world
  559. player.ReleaseNetworkControls();
  560. HandleBody(player);
  561. // remove player from server
  562. GetGame().DisconnectPlayer(identity, uid);
  563. // Send list of players at all clients
  564. GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(SyncEvents.SendPlayerList, 1000);
  565. }
  566. bool ShouldPlayerBeKilled(PlayerBase player)
  567. {
  568. if (player.IsUnconscious() || player.IsRestrained())
  569. {
  570. switch (player.GetKickOffReason())
  571. {
  572. case EClientKicked.SERVER_EXIT:
  573. return false;
  574. case EClientKicked.KICK_ALL_ADMIN:
  575. return false;
  576. case EClientKicked.KICK_ALL_SERVER:
  577. return false;
  578. case EClientKicked.SERVER_SHUTDOWN:
  579. return false;
  580. default:
  581. return true;
  582. }
  583. }
  584. return false;
  585. }
  586. void HandleBody(PlayerBase player)
  587. {
  588. if (player.IsAlive())
  589. {
  590. if (ShouldPlayerBeKilled(player))
  591. {
  592. PluginAdminLog adm = PluginAdminLog.Cast(GetPlugin(PluginAdminLog));
  593. adm.PlayerKilledByDisconnect(player);
  594. player.SetHealth("", "", 0.0);//kill
  595. }
  596. else
  597. {
  598. player.Delete();// remove the body
  599. }
  600. }
  601. }
  602. void TickScheduler(float timeslice)
  603. {
  604. GetGame().GetWorld().GetPlayerList(m_Players);
  605. int players_count = m_Players.Count();
  606. int tick_count_max = Math.Min(players_count, SCHEDULER_PLAYERS_PER_TICK);
  607. for (int i = 0; i < tick_count_max; i++)
  608. {
  609. if (m_currentPlayer >= players_count)
  610. {
  611. m_currentPlayer = 0;
  612. }
  613. PlayerBase currentPlayer = PlayerBase.Cast(m_Players.Get(m_currentPlayer));
  614. if (currentPlayer)
  615. currentPlayer.OnTick();
  616. m_currentPlayer++;
  617. }
  618. }
  619. //--------------------------------------------------
  620. override bool InsertCorpse(Man player)
  621. {
  622. CorpseData corpse_data = new CorpseData(PlayerBase.Cast(player),GetGame().GetTime());
  623. return m_DeadPlayersArray.Insert(corpse_data) >= 0;
  624. }
  625. void UpdateCorpseStatesServer()
  626. {
  627. if (m_DeadPlayersArray.Count() == 0)//nothing to process, abort
  628. return;
  629. int current_time = GetGame().GetTime();
  630. array<int> invalid_corpses = new array<int>;
  631. CorpseData corpse_data;
  632. for (int i = 0; i < m_DeadPlayersArray.Count(); i++)
  633. {
  634. corpse_data = m_DeadPlayersArray.Get(i);
  635. if (!corpse_data || (corpse_data && (!corpse_data.m_Player || !corpse_data.m_bUpdate)))
  636. {
  637. invalid_corpses.Insert(i);
  638. }
  639. else if (corpse_data.m_bUpdate && current_time - corpse_data.m_iLastUpdateTime >= 30000)
  640. {
  641. corpse_data.UpdateCorpseState();
  642. corpse_data.m_iLastUpdateTime = current_time;
  643. }
  644. }
  645. //cleanup
  646. if (invalid_corpses.Count() > 0)
  647. {
  648. for (i = invalid_corpses.Count() - 1; i > -1; i--)
  649. {
  650. m_DeadPlayersArray.Remove(invalid_corpses.Get(i));
  651. }
  652. }
  653. }
  654. //--------------------------------------------------
  655. override void SyncRespawnModeInfo(PlayerIdentity identity)
  656. {
  657. ScriptRPC rpc = new ScriptRPC();
  658. rpc.Write(m_RespawnMode);
  659. rpc.Send(null, ERPCs.RPC_SERVER_RESPAWN_MODE, true, identity);
  660. }
  661. override RainProcurementHandler GetRainProcurementHandler()
  662. {
  663. return m_RainProcHandler;
  664. }
  665. override array<vector> GetActiveRefresherLocations()
  666. {
  667. return m_ActiveRefresherLocations;
  668. }
  669. //! DEPRECATED
  670. PluginAdditionalInfo m_moduleDefaultCharacter;
  671. }