//! int time of the logout end //: string uid of the player typedef Param2 LogoutInfo; class MissionServer extends MissionBase { ref array m_Players; ref array m_DeadPlayersArray; ref map m_LogoutPlayers; ref map m_NewLogoutPlayers; ref RainProcurementHandler m_RainProcHandler; const int SCHEDULER_PLAYERS_PER_TICK = 5; int m_currentPlayer; int m_RespawnMode; // ----------------------- // ARTILLERY SOUNDS SETUP // ----------------------- private float m_ArtyBarrageTimer = 0; // This is not to be edited in Init.c this is just to increment time // Variables to be modified in Init.c protected bool m_PlayArty = false; // Toggle if Off map artillery sounds are played protected float m_ArtyDelay = 0; // Set how much time there is between two barrages (in seconds) protected int m_MinSimultaneousStrikes = 0; // The MIN of simultaneous shots on the map (Will be clamped between 1 and max shots) protected int m_MaxSimultaneousStrikes = 0; // The MAX of simultaneous shots on the map (Will be clamped between 1 and max amount of coords) protected ref array m_FiringPos; // Where we should fire from. On Init set the relevant data //All Chernarus firing coordinates protected const ref array CHERNARUS_STRIKE_POS = { "-500.00 165.00 5231.69", "-500.00 300.00 9934.41", "10406.86 192.00 15860.00", "4811.75 370.00 15860.00", "-500.00 453.00 15860.00" }; //All livonia firing coordinates protected const ref array LIVONIA_STRIKE_POS = { "7440.00 417.00 -500.00", "-500.00 276.00 5473.00", "-500.00 265.00 9852.00", "4953.00 240.00 13300.00", "9620.00 188.00 13300.00", "13300.00 204.00 10322.00", "13300.00 288.00 6204.00", "13300.00 296.00 -500.00" }; // ----------------------- // END OF ARTILLERY SETUP // ----------------------- PlayerBase m_player; MissionBase m_mission; void MissionServer() { GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.UpdatePlayersStats, 30000, true); m_DeadPlayersArray = new array; UpdatePlayersStats(); m_Players = new array; m_LogoutPlayers = new map; m_NewLogoutPlayers = new map; m_RainProcHandler = new RainProcurementHandler(this); m_ActiveRefresherLocations = new array(); } void ~MissionServer() { GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).Remove(this.UpdatePlayersStats); } override void OnInit() { super.OnInit(); CfgGameplayHandler.LoadData(); PlayerSpawnHandler.LoadData(); CfgPlayerRestrictedAreaHandler.LoadData(); UndergroundAreaLoader.SpawnAllTriggerCarriers(); //Either pass consts in Init.c or insert all desired coords (or do both ;)) m_FiringPos = new array(); } override void OnMissionStart() { super.OnMissionStart(); // We will load the Effect areas on Default mission start EffectAreaLoader.CreateZones(); } override void OnUpdate(float timeslice) { UpdateDummyScheduler(); TickScheduler(timeslice); UpdateLogoutPlayers(); m_WorldData.UpdateBaseEnvTemperature(timeslice); // re-calculate base enviro temperature m_RainProcHandler.Update(timeslice); RandomArtillery(timeslice); super.OnUpdate(timeslice); } override void OnGameplayDataHandlerLoad() { m_RespawnMode = CfgGameplayHandler.GetDisableRespawnDialog(); GetGame().SetDebugMonitorEnabled(GetGame().ServerConfigGetInt("enableDebugMonitor")); InitialiseWorldData(); } void RandomArtillery(float deltaTime) { // ARTY barrage if (m_PlayArty) { // We only perform timer checks and increments if we enabled the artillery barrage if (m_ArtyBarrageTimer > m_ArtyDelay) { //We clamp to guarantee 1 and never have multiple shots on same pos, even in case of entry error m_MaxSimultaneousStrikes = Math.Clamp(m_MaxSimultaneousStrikes, 1, m_FiringPos.Count()); m_MinSimultaneousStrikes = Math.Clamp(m_MinSimultaneousStrikes, 1, m_MaxSimultaneousStrikes); // Variables to be used in this scope int randPos; // Select random position Param1 pos; // The value to be sent through RPC array params; // The RPC params if (m_MaxSimultaneousStrikes == 1) { // We only have one set of coordinates to send randPos = Math.RandomIntInclusive(0, m_FiringPos.Count() - 1); pos = new Param1(m_FiringPos[randPos]); params = new array; params.Insert(pos); GetGame().RPC(null, ERPCs.RPC_SOUND_ARTILLERY, params, true); } else { //We will do some extra steps to /* 1. Send multiple coords (Send one RPC per coord set) 2. Ensure we don't have duplicates */ array usedIndices = new array; // Will store all previusly fired upon indices // We determine how many positions fire between MIN and MAX int randFireNb = Math.RandomIntInclusive(m_MinSimultaneousStrikes, m_MaxSimultaneousStrikes); for (int i = 0; i < randFireNb; i++) { randPos = Math.RandomIntInclusive(0, m_FiringPos.Count() - 1); if (usedIndices.Count() <= 0 || usedIndices.Find(randPos) < 0) //We do not find the index or array is empty { // We prepare to send the message pos = new Param1(m_FiringPos[randPos]); params = new array; // We send the message with this set of coords params.Insert(pos); GetGame().RPC(null, ERPCs.RPC_SOUND_ARTILLERY, params, true); // We store the last used value usedIndices.Insert(randPos); } } } // Reset timer for new loop m_ArtyBarrageTimer = 0.0; } m_ArtyBarrageTimer += deltaTime; } } override bool IsServer() { return true; } override bool IsPlayerDisconnecting(Man player) { return (m_LogoutPlayers && m_LogoutPlayers.Contains(PlayerBase.Cast(player))) || (m_NewLogoutPlayers && m_NewLogoutPlayers.Contains(PlayerBase.Cast(player))); } void UpdatePlayersStats() { PluginLifespan moduleLifespan; Class.CastTo(moduleLifespan, GetPlugin(PluginLifespan)); array players = new array(); GetGame().GetPlayers(players); foreach (Man man : players) { PlayerBase player; if (Class.CastTo(player, man)) { player.StatUpdateByTime(AnalyticsManagerServer.STAT_PLAYTIME); player.StatUpdateByPosition(AnalyticsManagerServer.STAT_DISTANCE); moduleLifespan.UpdateLifespan(player); } } UpdateCorpseStatesServer(); } protected void AddNewPlayerLogout(PlayerBase player, notnull LogoutInfo info) { m_LogoutPlayers.Insert(player, info); m_NewLogoutPlayers.Remove(player); } // check if logout finished for some players void UpdateLogoutPlayers() { for (int i = 0; i < m_LogoutPlayers.Count();) { LogoutInfo info = m_LogoutPlayers.GetElement(i); if (GetGame().GetTime() >= info.param1) { PlayerIdentity identity; PlayerBase player = m_LogoutPlayers.GetKey(i); if (player) { identity = player.GetIdentity(); m_LogoutPlayers.Remove(player); } else { m_LogoutPlayers.RemoveElement(i); } // disable reconnecting to old char // GetGame().RemoveFromReconnectCache(info.param2); PlayerDisconnected(player, identity, info.param2); } else { ++i; } } } override void OnEvent(EventType eventTypeId, Param params) { PlayerIdentity identity; PlayerBase player; int counter = 0; switch (eventTypeId) { case ClientPrepareEventTypeID: ClientPrepareEventParams clientPrepareParams; Class.CastTo(clientPrepareParams, params); CfgGameplayHandler.SyncDataSendEx(clientPrepareParams.param1); UndergroundAreaLoader.SyncDataSend(clientPrepareParams.param1); CfgPlayerRestrictedAreaHandler.SyncDataSend(clientPrepareParams.param1); OnClientPrepareEvent(clientPrepareParams.param1, clientPrepareParams.param2, clientPrepareParams.param3, clientPrepareParams.param4, clientPrepareParams.param5); break; case ClientNewEventTypeID: ClientNewEventParams newParams; Class.CastTo(newParams, params); player = OnClientNewEvent(newParams.param1, newParams.param2, newParams.param3); if (!player) { Debug.Log("ClientNewEvent: Player is empty"); return; } identity = newParams.param1; InvokeOnConnect(player,identity); SyncEvents.SendPlayerList(); ControlPersonalLight(player); SyncGlobalLighting(player); break; case ClientReadyEventTypeID: ClientReadyEventParams readyParams; Class.CastTo(readyParams, params); identity = readyParams.param1; Class.CastTo(player, readyParams.param2); if (!player) { Debug.Log("ClientReadyEvent: Player is empty"); return; } OnClientReadyEvent(identity, player); InvokeOnConnect(player, identity); // Send list of players at all clients SyncEvents.SendPlayerList(); ControlPersonalLight(player); SyncGlobalLighting(player); break; case ClientRespawnEventTypeID: ClientRespawnEventParams respawnParams; Class.CastTo(respawnParams, params); identity = respawnParams.param1; Class.CastTo(player, respawnParams.param2); if (!player) { Debug.Log("ClientRespawnEvent: Player is empty"); return; } OnClientRespawnEvent(identity, player); break; case ClientReconnectEventTypeID: ClientReconnectEventParams reconnectParams; Class.CastTo(reconnectParams, params); identity = reconnectParams.param1; Class.CastTo(player, reconnectParams.param2); if (!player) { Debug.Log("ClientReconnectEvent: Player is empty"); return; } OnClientReconnectEvent(identity, player); break; case ClientDisconnectedEventTypeID: ClientDisconnectedEventParams discoParams; Class.CastTo(discoParams, params); identity = discoParams.param1; Class.CastTo(player, discoParams.param2); int logoutTime = discoParams.param3; bool authFailed = discoParams.param4; if (!player) { Debug.Log("ClientDisconnectenEvent: Player is empty"); return; } OnClientDisconnectedEvent(identity, player, logoutTime, authFailed); break; case LogoutCancelEventTypeID: LogoutCancelEventParams logoutCancelParams; Class.CastTo(logoutCancelParams, params); Class.CastTo(player, logoutCancelParams.param1); identity = player.GetIdentity(); if (identity) { // disable reconnecting to old char // GetGame().RemoveFromReconnectCache(identity.GetId()); Print("[Logout]: Player " + identity.GetId() + " cancelled"); } else { Print("[Logout]: Player cancelled"); } m_LogoutPlayers.Remove(player); m_NewLogoutPlayers.Remove(player); break; } } void InvokeOnConnect(PlayerBase player, PlayerIdentity identity) { Debug.Log("InvokeOnConnect:"+this.ToString(),"Connect"); if (player) player.OnConnect(); } void InvokeOnDisconnect(PlayerBase player) { Debug.Log("InvokeOnDisconnect:"+this.ToString(),"Connect"); if (player) player.OnDisconnect(); } void OnClientPrepareEvent(PlayerIdentity identity, out bool useDB, out vector pos, out float yaw, out int preloadTimeout) { if (GetHive()) { // use character from database useDB = true; } else { // use following data without database useDB = false; pos = "1189.3 0.0 5392.48"; yaw = 0; } } // Enables/Disables personal light on the given player. void ControlPersonalLight(PlayerBase player) { if (player) { bool is_personal_light = ! GetGame().ServerConfigGetInt("disablePersonalLight"); Param1 personal_light_toggle = new Param1(is_personal_light); GetGame().RPCSingleParam(player, ERPCs.RPC_TOGGLE_PERSONAL_LIGHT, personal_light_toggle, true, player.GetIdentity()); } else { Error("Error! Player was not initialized at the right time. Thus cannot send RPC command to enable or disable personal light!"); } } // syncs global lighting setup from the server (lightingConfig server config parameter) void SyncGlobalLighting(PlayerBase player) { if (player) { int lightingID = GetGame().ServerConfigGetInt("lightingConfig"); Param1 lightID = new Param1(lightingID); GetGame().RPCSingleParam(player, ERPCs.RPC_SEND_LIGHTING_SETUP, lightID, true, player.GetIdentity()); } } //! returns whether received data is valid, ctx can be filled on client in StoreLoginData() bool ProcessLoginData(ParamsReadContext ctx) { //creates temporary server-side structure for handling default character spawn return GetGame().GetMenuDefaultCharacterData(false).DeserializeCharacterData(ctx); } // PlayerBase CreateCharacter(PlayerIdentity identity, vector pos, ParamsReadContext ctx, string characterName) { Entity playerEnt; playerEnt = GetGame().CreatePlayer(identity, characterName, pos, 0, "NONE");//Creates random player Class.CastTo(m_player, playerEnt); GetGame().SelectPlayer(identity, m_player); return m_player; } //! Spawns character equip from received data. Checks validity against config, randomizes if invalid value and config array not empty. void EquipCharacter(MenuDefaultCharacterData char_data) { int slot_ID; string attachment_type; for (int i = 0; i < DefaultCharacterCreationMethods.GetAttachmentSlotsArray().Count(); i++) { slot_ID = DefaultCharacterCreationMethods.GetAttachmentSlotsArray().Get(i); attachment_type = ""; 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 { //randomize if (DefaultCharacterCreationMethods.GetConfigArrayCountFromSlotID(slot_ID) > 0) { attachment_type = DefaultCharacterCreationMethods.GetConfigAttachmentTypes(slot_ID).GetRandomElement(); } else //undefined, moving on continue; } if (attachment_type != "") { m_player.GetInventory().CreateAttachmentEx(attachment_type,slot_ID); } } StartingEquipSetup(m_player, true); } //! can be overriden to manually set up starting equip. 'clothesChosen' is legacy parameter, does nothing. void StartingEquipSetup(PlayerBase player, bool clothesChosen) { } bool VerifyAttachmentType(int slot_ID, string attachment_type) { return DefaultCharacterCreationMethods.GetConfigAttachmentTypes(slot_ID).Find(attachment_type) > -1; } PlayerBase OnClientNewEvent(PlayerIdentity identity, vector pos, ParamsReadContext ctx) { string characterType = GetGame().CreateRandomPlayer(); bool generateRandomEquip = false; // get login data for new character if (ProcessLoginData(ctx) && (m_RespawnMode == GameConstants.RESPAWN_MODE_CUSTOM) && !GetGame().GetMenuDefaultCharacterData(false).IsRandomCharacterForced()) { if (GetGame().ListAvailableCharacters().Find(GetGame().GetMenuDefaultCharacterData().GetCharacterType()) > -1) characterType = GetGame().GetMenuDefaultCharacterData().GetCharacterType(); } else { generateRandomEquip = true; } if (PlayerSpawnHandler.IsInitialized()) { PlayerSpawnPreset presetData = PlayerSpawnHandler.GetRandomCharacterPreset(); if (presetData && presetData.IsValid()) { string presetCharType = presetData.GetRandomCharacterType(); if (presetCharType == string.Empty) presetCharType = characterType; if (CreateCharacter(identity, pos, ctx, presetCharType) != null) { PlayerSpawnHandler.ProcessEquipmentData(m_player,presetData); return m_player; } else { ErrorEx("Failed to create character from type: " + presetCharType + ", using default spawning method"); } } else { ErrorEx("Failed to load PlayerSpawnPreset data properly, using default spawning method"); } } if (CreateCharacter(identity, pos, ctx, characterType)) { if (generateRandomEquip) GetGame().GetMenuDefaultCharacterData().GenerateRandomEquip(); EquipCharacter(GetGame().GetMenuDefaultCharacterData()); } return m_player; } void OnClientReadyEvent(PlayerIdentity identity, PlayerBase player) { GetGame().SelectPlayer(identity, player); #ifdef DIAG_DEVELOPER if (FeatureTimeAccel.m_CurrentTimeAccel) { GetGame().RPCSingleParam(player, ERPCs.DIAG_TIMEACCEL_CLIENT_SYNC, FeatureTimeAccel.m_CurrentTimeAccel, true, identity); } #endif } void OnClientRespawnEvent(PlayerIdentity identity, PlayerBase player) { if (player) { if (player.IsUnconscious() || player.IsRestrained()) { PluginAdminLog adm = PluginAdminLog.Cast(GetPlugin(PluginAdminLog)); adm.PlayerKilledByRespawn(player); // kill character player.SetHealth("", "", 0.0); } } #ifdef DIAG_DEVELOPER if (FeatureTimeAccel.m_CurrentTimeAccel) { GetGame().RPCSingleParam(player, ERPCs.DIAG_TIMEACCEL_CLIENT_SYNC, FeatureTimeAccel.m_CurrentTimeAccel, true, identity); } #endif } void OnClientReconnectEvent(PlayerIdentity identity, PlayerBase player) { if (player) { player.OnReconnect(); } } void OnClientDisconnectedEvent(PlayerIdentity identity, PlayerBase player, int logoutTime, bool authFailed) { bool disconnectNow = true; // TODO: get out of vehicle // using database and no saving if authorization failed if (GetHive() && !authFailed) { if (player.IsAlive()) { if (!m_LogoutPlayers.Contains(player) && !m_NewLogoutPlayers.Contains(player)) { Print("[Logout]: New player " + identity.GetId() + " with logout time " + logoutTime.ToString()); // send statistics to client player.StatSyncToClient(); // inform client about logout time GetGame().SendLogoutTime(player, logoutTime); // wait for some time before logout and save LogoutInfo params = new LogoutInfo(GetGame().GetTime() + logoutTime * 1000, identity.GetId()); m_NewLogoutPlayers.Insert(player, params); GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(AddNewPlayerLogout, 0, false, player, params); // allow reconnecting to old char only if not in cars, od ladders etc. as they cannot be properly synchronized for reconnect //if (!player.GetCommand_Vehicle() && !player.GetCommand_Ladder()) //{ // GetGame().AddToReconnectCache(identity); //} // wait until logout timer runs out disconnectNow = false; } return; } } if (disconnectNow) { Print("[Logout]: New player " + identity.GetId() + " with instant logout"); // inform client about instant logout GetGame().SendLogoutTime(player, 0); PlayerDisconnected(player, identity, identity.GetId()); } } void PlayerDisconnected(PlayerBase player, PlayerIdentity identity, string uid) { // Note: At this point, identity can be already deleted if (!player) { Print("[Logout]: Skipping player " + uid + ", already removed"); return; } // disable reconnecting to old char //GetGame().RemoveFromReconnectCache(uid); // now player can't cancel logout anymore, so call everything needed upon disconnect InvokeOnDisconnect(player); Print("[Logout]: Player " + uid + " finished"); if (GetHive()) { // save player player.Save(); // unlock player in DB GetHive().CharacterExit(player); } // handle player's existing char in the world player.ReleaseNetworkControls(); HandleBody(player); // remove player from server GetGame().DisconnectPlayer(identity, uid); // Send list of players at all clients GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(SyncEvents.SendPlayerList, 1000); } bool ShouldPlayerBeKilled(PlayerBase player) { if (player.IsUnconscious() || player.IsRestrained()) { switch (player.GetKickOffReason()) { case EClientKicked.SERVER_EXIT: return false; case EClientKicked.KICK_ALL_ADMIN: return false; case EClientKicked.KICK_ALL_SERVER: return false; case EClientKicked.SERVER_SHUTDOWN: return false; default: return true; } } return false; } void HandleBody(PlayerBase player) { if (player.IsAlive()) { if (ShouldPlayerBeKilled(player)) { PluginAdminLog adm = PluginAdminLog.Cast(GetPlugin(PluginAdminLog)); adm.PlayerKilledByDisconnect(player); player.SetHealth("", "", 0.0);//kill } else { player.Delete();// remove the body } } } void TickScheduler(float timeslice) { GetGame().GetWorld().GetPlayerList(m_Players); int players_count = m_Players.Count(); int tick_count_max = Math.Min(players_count, SCHEDULER_PLAYERS_PER_TICK); for (int i = 0; i < tick_count_max; i++) { if (m_currentPlayer >= players_count) { m_currentPlayer = 0; } PlayerBase currentPlayer = PlayerBase.Cast(m_Players.Get(m_currentPlayer)); if (currentPlayer) currentPlayer.OnTick(); m_currentPlayer++; } } //-------------------------------------------------- override bool InsertCorpse(Man player) { CorpseData corpse_data = new CorpseData(PlayerBase.Cast(player),GetGame().GetTime()); return m_DeadPlayersArray.Insert(corpse_data) >= 0; } void UpdateCorpseStatesServer() { if (m_DeadPlayersArray.Count() == 0)//nothing to process, abort return; int current_time = GetGame().GetTime(); array invalid_corpses = new array; CorpseData corpse_data; for (int i = 0; i < m_DeadPlayersArray.Count(); i++) { corpse_data = m_DeadPlayersArray.Get(i); if (!corpse_data || (corpse_data && (!corpse_data.m_Player || !corpse_data.m_bUpdate))) { invalid_corpses.Insert(i); } else if (corpse_data.m_bUpdate && current_time - corpse_data.m_iLastUpdateTime >= 30000) { corpse_data.UpdateCorpseState(); corpse_data.m_iLastUpdateTime = current_time; } } //cleanup if (invalid_corpses.Count() > 0) { for (i = invalid_corpses.Count() - 1; i > -1; i--) { m_DeadPlayersArray.Remove(invalid_corpses.Get(i)); } } } //-------------------------------------------------- override void SyncRespawnModeInfo(PlayerIdentity identity) { ScriptRPC rpc = new ScriptRPC(); rpc.Write(m_RespawnMode); rpc.Send(null, ERPCs.RPC_SERVER_RESPAWN_MODE, true, identity); } override RainProcurementHandler GetRainProcurementHandler() { return m_RainProcHandler; } override array GetActiveRefresherLocations() { return m_ActiveRefresherLocations; } //! DEPRECATED PluginAdditionalInfo m_moduleDefaultCharacter; }