using pepperspray.ChatServer.Game; using pepperspray.ChatServer.Protocol; using pepperspray.ChatServer.Services.Events; using pepperspray.CIO; using pepperspray.Mysql; using pepperspray.SharedServices; using RSG; using Serilog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; namespace pepperspray.ChatServer.Services { internal class LobbyService : IDIService, PlayerLoggedOffEvent.IListener { private TimeSpan WorldChatIntermessageInterval = TimeSpan.FromSeconds(5); private TimeSpan LobbyChatIntermessageInterval = TimeSpan.FromSeconds(1); private Configuration config; private ChatManager manager; private UserRoomService userRoomService; private UserLangService langServ; private ConcurrentDictionary> chatPlayerDates = new ConcurrentDictionary>(); Random random = new Random(); private string GetRandomHexColor() { return string.Format("{0:X6}", random.Next(0x1000000)); } public void Inject() { this.config = DI.Get(); this.manager = DI.Get(); this.userRoomService = DI.Get(); langServ = DI.Get(); } public void PlayerLoggedOff(PlayerLoggedOffEvent ev) { if (ev.Handle.CurrentLobby != null) { this.manager.Sink(this.Leave(ev.Handle, ev.Handle.CurrentLobby)); } this.cleanupSlowmodeTimer(ev.Handle); } internal bool PlayerCanJoinLobby(PlayerHandle player, Lobby lobby) { //if (player.AdminFlags.HasFlag(AdminFlags.RoomManagement)) if (player.User.IsAdminOrModerator) { return true; } if (lobby.IsUserRoom) { return this.userRoomService.PlayerCanJoinRoom(player, lobby.UserRoom); } else { return true; } } internal string[] JoinMessages = { " joined", " has come", " is logged in", " joined the party", " entered the room", " has arrived, everyone say hello", " is out!", " has landed", " came with a pizza" }; internal string[] LeaveMessages = { " left us", " flew out of the room", " gone", " left", " turned into a bird and flew", " ran away" }; private string GetRandomJoinMessage(string playerName) { return $"« {playerName}{JoinMessages[random.Next(0, JoinMessages.Length)]} »"; } private string GetRandomLeaveMessage(string playerName) { return $"« {playerName}{LeaveMessages[random.Next(0, LeaveMessages.Length)]} »"; } internal IPromise Join(PlayerHandle player, Lobby lobby) { // Check if the player is already in the lobby they're trying to join if (player.CurrentLobby == lobby) { return Nothing.Resolved(); } // Leave the player's current lobby, if they are in one if (player.CurrentLobby != null) { Leave(player, player.CurrentLobby); } // Add the player to the lobby and set their current lobby to the new lobby lock (this.manager) { lobby.AddPlayer(player); player.CurrentLobby = lobby; } // Create a list of promises to be combined later var promises = new List>(); // Add a promise to the list to send the "JoinedRoom" response to the player promises.Add(player.Stream.Write(Responses.JoinedRoom(lobby))); // Add a line to write a message when the player enters the room if (lobby.IsStandard || lobby.IsUserRoom || lobby.IsPrivateRoom) { var message = GetRandomJoinMessage(player.Character.Name); // Notify all players in the lobby about the new player foreach (var existing in lobby.Players) { if (existing != player) { promises.Add(existing.Stream.Write(Responses.NewPlayer(player))); } } foreach (var existing in lobby.Players) { promises.Add(existing.Stream.Write(Responses.ServerLocalChatMessage(this.manager, message))); if (existing != player) { promises.Add(player.Stream.Write(Responses.NewPlayer(existing))); } } } // Combine all promises into a single promise and return it return new CombinedPromise(promises); } internal IPromise NotifyExistingAboutNew(PlayerHandle existing, PlayerHandle newp, string message) { if (existing == null || newp == null) { Log.Error("[LobbyService.Join] New existing player or newp is NULL"); return Nothing.Resolved(); } try { // Add a line to write a message when the player enters the room if (existing.CurrentLobby == newp.CurrentLobby) { existing.Stream.Write(Responses.ServerLocalChatMessage(this.manager, message)); } return Nothing.Resolved(); } catch { Log.Error($"[LobbyService.Join] ERROR to notify player about new. Player: {(existing != null && existing.Character != null ? existing.Character.Name : "NULL")}. NewPlayer: {(newp != null && newp.Character != null ? newp.Character.Name : "NULL")}"); return Nothing.Resolved(); } } internal IPromise NotifyNewAboutExisting(PlayerHandle existing, PlayerHandle newp, string message) { if (existing == null || newp == null) { Log.Error("[LobbyService.Join] New existing player or newp is NULL"); return Nothing.Resolved(); } try { // Add a line to write a message when the player enters the room if (newp.CurrentLobby == existing.CurrentLobby) { newp.Stream.Write(Responses.ServerLocalChatMessage(this.manager, message)); } return newp.Stream.Write(Responses.NewPlayer(existing)); } catch { Log.Error($"[LobbyService.Join] ERROR to notify new player about existing. Player: {(existing != null && existing.Character != null ? existing.Character.Name : "NULL")}. NewPlayer: {(newp != null && newp.Character != null ? newp.Character.Name : "NULL")}"); return Nothing.Resolved(); } } internal IPromise Leave(PlayerHandle player, Lobby lobby) { // Check if the player is in the lobby they're trying to leave if (player.CurrentLobby != lobby) { return Nothing.Resolved(); } // Create a list of promises to be combined later var promises = new List>(); lock (this.manager) { lobby.RemovePlayer(player); player.CurrentLobby = null; // Check if the lobby should be removed from the world if (lobby.Players.Count() == 0) { this.manager.World.RemoveLobby(lobby); } } // Dispatch the PlayerLeftLobbyEvent event this.manager.DispatchEvent(new PlayerLeftLobbyEvent { Handle = player, Lobby = lobby }); // Add a promise to the list to send the "JoinedLobby" response to the player promises.Add(player.Stream.Write(Responses.JoinedLobby())); // Add a promise to the list to notify other players in the lobby about the leaving player foreach (var existing in lobby.Players) { var message = GetRandomLeaveMessage(player.Character.Name); promises.Add(existing.Stream.Write(Responses.ServerLocalChatMessage(this.manager, message))); promises.Add(existing.Stream.Write(Responses.PlayerLeave(player))); } // Combine all promises into a single promise and return it return new CombinedPromise(promises); } internal IPromise NotifyLobbyAboutLeavingPlayer(PlayerHandle player, Lobby lobby) { PlayerHandle[] lobbyPlayers = null; lock (this.manager) { lobbyPlayers = lobby.Players.ToArray(); } return new CombinedPromise( lobbyPlayers.Select(p => p.Stream.Write(Responses.PlayerLeave(player)))); } internal bool CheckSlowmodeTimer(Lobby lobby, PlayerHandle handle) { //bool flag = handle.AdminFlags.HasFlag(AdminFlags.ChatManagement); bool flag = handle.User.IsAdminOrModerator || handle.User.IsFounder; if (lobby.IsUserRoom && this.userRoomService.PlayerCanModerateRoom(handle, lobby.UserRoom)) { flag = true; } if (flag) { return true; } if (lobby.IsUserRoom && lobby.UserRoom.IsMuted) { this.manager.Sink(handle.Stream.Write(Responses.ServerLocalChatMessage(this.manager, this.langServ.GetLocale(handle.User, "SORRY_CHAT_IS_MUTED", Array.Empty())))); return false; } UserRoom userRoom = lobby.UserRoom; TimeSpan interval = (userRoom != null) ? userRoom.SlowmodeInterval : this.LobbyChatIntermessageInterval; if (this.checkSlowmodeTimer(lobby.Identifier, handle, interval)) { return true; } this.manager.Sink(handle.Stream.Write(Responses.ServerLocalChatMessage(this.manager, this.langServ.GetLocale(handle.User, "TOO_FAST_CHAT_IS_IN_SLOWMODE", new object[] { interval.TotalSeconds })))); return false; } internal bool CheckSlowmodeTimerInWorld(PlayerHandle handle) { //if (handle.AdminFlags.HasFlag(AdminFlags.ChatManagement)) if (handle.User.IsAdminOrModerator || handle.User.IsFounder) { return true; } if (this.checkSlowmodeTimer("world", handle, this.WorldChatIntermessageInterval)) { return true; } this.manager.Sink(handle.Stream.Write(Responses.ServerWorldChatMessage(this.manager, this.langServ.GetLocale(handle.User, "TOO_FAST_CHAT_IS_IN_SLOWMODE", new object[] { this.WorldChatIntermessageInterval })))); return false; } private bool checkSlowmodeTimer(string identifier, PlayerHandle handle, TimeSpan interval) { if (!this.chatPlayerDates.TryGetValue(handle, out ConcurrentDictionary dateTimes)) { dateTimes = new ConcurrentDictionary(); this.chatPlayerDates[handle] = dateTimes; } if (!dateTimes.TryGetValue(identifier, out DateTime date)) { date = DateTime.MinValue; } if ((DateTime.Now - date) > interval) { dateTimes[identifier] = DateTime.Now; return true; } else { return false; } } private void cleanupSlowmodeTimer(PlayerHandle handle) { ConcurrentDictionary var; this.chatPlayerDates.TryRemove(handle, out var); } } }