From 271cb87f9e501b05e9f5568bb38db487e3eb477f Mon Sep 17 00:00:00 2001 From: Waverunner Date: Fri, 10 Jul 2015 10:40:38 -0400 Subject: [PATCH] Changed ChatService to a manager and moved chat room functionality to ChatRoomService under ChatManager NotifyPlayersPacketIntent can now accept a list of networkId's to notify players with a packet --- src/intents/NotifyPlayersPacketIntent.java | 23 +- .../{ChatService.java => ChatManager.java} | 384 +-------------- src/services/chat/ChatRoomService.java | 458 ++++++++++++++++++ src/services/galaxy/GalacticManager.java | 10 +- src/services/galaxy/GameManager.java | 1 - src/services/player/PlayerManager.java | 96 +++- 6 files changed, 581 insertions(+), 391 deletions(-) rename src/services/chat/{ChatService.java => ChatManager.java} (56%) create mode 100644 src/services/chat/ChatRoomService.java diff --git a/src/intents/NotifyPlayersPacketIntent.java b/src/intents/NotifyPlayersPacketIntent.java index ce2674f42..64da68fae 100644 --- a/src/intents/NotifyPlayersPacketIntent.java +++ b/src/intents/NotifyPlayersPacketIntent.java @@ -32,19 +32,35 @@ import resources.Terrain; import resources.control.Intent; import resources.player.Player; +import java.util.List; + public class NotifyPlayersPacketIntent extends Intent { public static final String TYPE = "GalaxyWidePacketIntent"; private Packet packet; private Terrain terrain; + private List networkIds; private ConditionalNotify condition; - - public NotifyPlayersPacketIntent(Packet packet, Terrain terrain, ConditionalNotify condition) { + + public NotifyPlayersPacketIntent(Packet packet, Terrain terrain, ConditionalNotify condition, List networkIds) { super(TYPE); this.packet = packet; this.terrain = terrain; this.condition = condition; + this.networkIds = networkIds; + } + + public NotifyPlayersPacketIntent(Packet packet, Terrain terrain, ConditionalNotify condition) { + this(packet, terrain, condition, null); + } + + public NotifyPlayersPacketIntent(Packet packet, ConditionalNotify condition, List networkIds) { + this(packet, null, condition, networkIds); + } + + public NotifyPlayersPacketIntent(Packet packet, List networkIds) { + this(packet, null, null, networkIds); } public NotifyPlayersPacketIntent(Packet packet, Terrain terrain) { @@ -52,12 +68,13 @@ public class NotifyPlayersPacketIntent extends Intent { } public NotifyPlayersPacketIntent(Packet p) { - this(p, null, null); + this(p, null, null, null); } public Packet getPacket() { return packet; } public Terrain getTerrain() { return terrain; } public ConditionalNotify getCondition() { return condition; } + public List getNetworkIds() { return networkIds; } public interface ConditionalNotify { boolean meetsCondition(Player player); diff --git a/src/services/chat/ChatService.java b/src/services/chat/ChatManager.java similarity index 56% rename from src/services/chat/ChatService.java rename to src/services/chat/ChatManager.java index 8ae6e9863..764175354 100644 --- a/src/services/chat/ChatService.java +++ b/src/services/chat/ChatManager.java @@ -50,6 +50,7 @@ import resources.chat.ChatResult; import resources.chat.ChatRoom; import resources.collections.SWGList; import resources.control.Intent; +import resources.control.Manager; import resources.control.Service; import resources.encodables.OutOfBandPackage; import resources.encodables.ProsePackage; @@ -74,26 +75,18 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class ChatService extends Service { +public class ChatManager extends Manager { - // Array of only the valid terrains in relation to zones able to use planetary-based chat - private static final List terrains = Arrays.asList( - Terrain.CORELLIA, Terrain.DANTOOINE, Terrain.DATHOMIR, Terrain.ENDOR, Terrain.KASHYYYK, Terrain.LOK, - Terrain.MUSTAFAR, Terrain.NABOO, Terrain.RORI, Terrain.TATOOINE, Terrain.TALUS, Terrain.YAVIN4 - ); private ObjectDatabase mails; private int maxMailId; - private int maxChatRoomId; - private Map roomMap; - // Map to keep track of each player's recent message for a room to prevent duplicates from client - private Map> messages; + private ChatRoomService roomService; - public ChatService() { - roomMap = new HashMap<>(); + public ChatManager() { + roomService = new ChatRoomService(); mails = new CachedObjectDatabase<>("odb/mails.db"); maxMailId = 1; - maxChatRoomId = 1; - messages = new ConcurrentHashMap<>(); + + addChildService(roomService); } @Override @@ -106,14 +99,11 @@ public class ChatService extends Service { registerForIntent(ServerStatusIntent.TYPE); registerForIntent(ChatAvatarRequestIntent.TYPE); registerForIntent(ZonePlayerSwapIntent.TYPE); - registerForIntent(ChatRoomUpdateIntent.TYPE); + mails.load(); - mails.traverse(new Traverser() { - @Override - public void process(Mail mail) { - if (mail.getId() >= maxMailId) - maxMailId = mail.getId() + 1; - } + mails.traverse(mail -> { + if (mail.getId() >= maxMailId) + maxMailId = mail.getId() + 1; }); return super.initialize(); } @@ -150,17 +140,6 @@ public class ChatService extends Service { if (i instanceof ChatAvatarRequestIntent) handleChatAvatarStatusRequestIntent((ChatAvatarRequestIntent) i); break; - case ChatRoomUpdateIntent.TYPE: - if (i instanceof ChatRoomUpdateIntent) - handleChatRoomUpdateIntent((ChatRoomUpdateIntent) i); - break; - } - } - - private void handleChatRoomUpdateIntent(ChatRoomUpdateIntent i) { - switch(i.getUpdateType()) { - case CREATE: createRoom(i.getAvatar(), i.isPublic(), i.getPath(), i.getTitle()); - default: break; } } @@ -176,35 +155,6 @@ public class ChatService extends Service { private void processSwgPacket(PlayerManager pm, Player player, String galaxyName, SWGPacket p) { switch (p.getPacketType()) { - /* Chat Rooms */ - case CHAT_QUERY_ROOM: - if (p instanceof ChatQueryRoom) - handleChatQueryRoom(player, (ChatQueryRoom) p); - break; - case CHAT_ENTER_ROOM_BY_ID: - if (!(p instanceof ChatEnterRoomById)) - return; - { - ChatEnterRoomById enterRoomById = (ChatEnterRoomById) p; - enterChatChannel(player, enterRoomById.getRoomId(), enterRoomById.getSequence()); - } - break; - case CHAT_REMOVE_AVATAR_FROM_ROOM: - if (p instanceof ChatRemoveAvatarFromRoom) - leaveChatChannel(player, ((ChatRemoveAvatarFromRoom) p).getPath()); - break; - case CHAT_SEND_TO_ROOM: - if (p instanceof ChatSendToRoom) - handleChatSendToRoom(player, (ChatSendToRoom) p); - break; - case CHAT_REQUEST_ROOM_LIST: - if (p instanceof ChatRequestRoomList) - handleChatRoomListRequest(player); - break; - case CHAT_CREATE_ROOM: - if (p instanceof ChatCreateRoom) - handleChatCreateRoom(player, (ChatCreateRoom) p); - break; /* Mails */ case CHAT_PERSISTENT_MESSAGE_TO_SERVER: if (p instanceof ChatPersistentMessageToServer) @@ -235,12 +185,13 @@ public class ChatService extends Service { switch (intent.getEvent()) { case PE_ZONE_IN: - switchPlanetaryChatChannels(player); + roomService.enterPlanetaryChatChannels(player); break; case PE_FIRST_ZONE: sendPersistentMessageHeaders(player, intent.getGalaxy()); updateChatAvatarStatus(player, intent.getGalaxy(), true); - enterJoinedChatChannels(player); + if (player.getPlayerObject() != null) + roomService.enterChatChannels(player, player.getPlayerObject().getJoinedChannels()); break; case PE_LOGGED_OUT: if (player.getCreatureObject() == null) @@ -289,313 +240,8 @@ public class ChatService extends Service { } } - /* Chat Rooms */ - - private void handleChatCreateRoom(Player player, ChatCreateRoom p) { - String path = p.getRoomName(); - String title = p.getRoomTitle(); - ChatRoom room = getRoom(path); - - ChatResult result = ChatResult.SUCCESS; - if (room != null) - result = ChatResult.ROOM_ALREADY_EXISTS; - - if (result == ChatResult.SUCCESS) { - room = createRoom(ChatAvatar.getFromPlayer(player), p.isPublic(), path, title); - room.setMuted(p.isModerated()); - } - - player.sendPacket(new ChatOnCreateRoom(result.getCode(), room, p.getSequence())); - } - - private void handleChatSendToRoom(Player player, ChatSendToRoom p) { - ChatResult result = ChatResult.SUCCESS; - - ChatRoom room = getRoom(p.getRoomId()); - if (room == null) - result = ChatResult.ROOM_INVALID_ID; - - if (result != ChatResult.SUCCESS) { - player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence())); - return; - } - - if (!incrementMessageCounter(player.getNetworkId(), room.getId(), p.getSequence())) - return; - - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - - result = room.canSendMessage(avatar); - - // TODO: Check length of messages -- Result 16 is used for too long message - - player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence())); - - if (result == ChatResult.SUCCESS) - room.sendMessage(avatar, p.getMessage(), p.getOutOfBandPackage(), player.getPlayerManager()); - } - - private void handleChatQueryRoom(Player player, ChatQueryRoom p) { - ChatRoom room = getRoom(p.getRoomPath()); // No result code is sent for queries - if (room == null) - return; - - player.sendPacket(new ChatQueryRoomResults(room, p.getSequence())); - } - - private void handleChatRoomListRequest(Player player) { - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - - List rooms = new ArrayList<>(); - for (ChatRoom chatRoom : roomMap.values()) { - if (!chatRoom.isPublic() && !chatRoom.getInvited().contains(avatar) && !chatRoom.getOwner().equals(avatar)) - continue; - rooms.add(chatRoom); - } - - ChatRoomList response = new ChatRoomList(rooms); - player.sendPacket(response); - } - - private void sendOnEnteredChatRoom(Player player, ChatAvatar avatar, ChatResult result, int id, int sequence) { - ChatOnEnteredRoom onEnteredRoom = new ChatOnEnteredRoom(avatar, id, sequence); - onEnteredRoom.setResult(result.getCode()); - player.sendPacket(onEnteredRoom); - } - - private void enterJoinedChatChannels(Player player) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - for (String s : ghost.getJoinedChannels()) { - enterChatChannel(player, s); - } - } - - private void switchPlanetaryChatChannels(Player player) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - // Leave old zone-only chat channels - String planetEndPath = ".Planet"; - for (String channel : ghost.getJoinedChannels()) { - if (channel.endsWith(planetEndPath)) { - leaveChatChannel(player, channel); - } else { - // Better way of doing this? - String[] split = channel.split("\\."); - if (split.length == 3 && split[2].equals("system")) - leaveChatChannel(player, channel); - } - } - - Terrain terrain = player.getCreatureObject().getLocation().getTerrain(); - - // Enter the new zone-only chat channels - if (!terrains.contains(terrain)) - return; - - String planetPath = "SWG." + player.getGalaxyName() + "." + terrain.getNameCapitalized() + "."; - enterChatChannel(player, planetPath + "Planet"); - enterChatChannel(player, planetPath + "system"); - } - - /** - * Attempts to join the specified chat channel - * @param player Player joining the chat channel - * @param room Chat room to enter - */ - public void enterChatChannel(Player player, ChatRoom room, int sequence) { - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) { - sendOnEnteredChatRoom(player, avatar, ChatResult.NONE, room.getId(), sequence); - return; - } - - ChatResult result = room.canJoinRoom(avatar); - if (result != ChatResult.SUCCESS && player.getAccessLevel() != AccessLevel.PLAYER) { - sendOnEnteredChatRoom(player, avatar, result, room.getId(), sequence); - return; - } - // TODO: Check if player is appropriate faction for the room (Rebel and imperial chat rooms) - - // Server-based list so we can join chat channels automatically - ghost.addJoinedChannel(room.getPath()); - - // Re-send the player the room list with just this room as it could have been private/hidden - // This also "refreshes" the client, not sending this will cause a Chat channel unavailable message. - ChatRoomList roomList = new ChatRoomList(room); - player.sendPacket(roomList); - - // Notify players of success, it's ChatResult.SUCCESS at this point - player.sendPacket(new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), sequence)); - - PlayerManager manager = player.getPlayerManager(); - // Notify everyone that a player entered the room - room.sendPacketToMembers(manager, new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), 0)); - - room.getMembers().add(avatar); - } - - public void enterChatChannel(Player player, int id, int sequence) { - ChatRoom room = getRoom(id); - if (room == null) { - sendOnEnteredChatRoom(player, ChatAvatar.getFromPlayer(player), ChatResult.NONE, id, sequence); - return; - } - enterChatChannel(player, room, sequence); - } - - public void enterChatChannel(Player player, String path) { - for (ChatRoom room : roomMap.values()) { - if (room.getPath().equals(path)) { - enterChatChannel(player, room, 0); - return; - } - } - System.err.println("Tried to join a room with a path that does not exist: " + path); - } - - public void leaveChatChannel(Player player, ChatRoom room, int sequence) { - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; // ChatOnLeaveRoom doesn't do anything other than for a ChatResult.SUCCESS, so no need to send a fail - - if (!room.getMembers().remove(avatar) && !ghost.removeJoinedChannel(room.getPath())) - return; - - player.sendPacket(new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), sequence)); - - room.sendPacketToMembers(player.getPlayerManager(), new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), 0)); - } - - public void leaveChatChannel(Player player, String path) { - for (ChatRoom chatRoom : roomMap.values()) { - if (chatRoom.getPath().equals(path)) - leaveChatChannel(player, chatRoom, 0); - } - } - /** - * Creates a new, non-persistent, chat room with the specified address path. - * @param creator Room creator who will also become the owner of this room - * @param isPublic Determines if the room should be publicly displayed in the channel listing - * @param path Address for the channel (Ex: SWG.serverName.Imperial) - * @param title Descriptive name of the chat channel (Ex: Imperial chat for this galaxy) - * @return {@link ChatRoom} - */ - public ChatRoom createRoom(ChatAvatar creator, boolean isPublic, String path, String title) { - if (path.isEmpty() || path.endsWith(".")) - return null; - - String base = "SWG." + creator.getGalaxy(); - if (!path.startsWith(base) || path.equals(base)) - return null; - - // All paths should have parents, lets validate to make sure they exist first. Create them if they don't. - int lastIndex = path.lastIndexOf("."); - if (lastIndex != -1) { - String parentPath = path.substring(0, lastIndex); - if (getRoom(parentPath) == null) { - createRoom(creator, isPublic, parentPath, ""); - } - } - - ChatRoom room = new ChatRoom(); - int id = maxChatRoomId; - maxChatRoomId++; - - room.setId(id); - room.setOwner(creator); - room.setCreator(creator); - room.setIsPublic(isPublic); - room.setPath(path); - room.setTitle(title); - - roomMap.put(id, room); - return room; - } - - private void createSystemChannels(String galaxy) { - ChatAvatar systemAvatar = ChatAvatar.getSystemAvatar(galaxy); - // TODO: Move these to a Server Data File? - String basePath = "SWG." + galaxy + "."; - createRoom(systemAvatar, true, basePath + "Galaxy", "public chat for the whole galaxy, cannot create rooms here"); - createRoom(systemAvatar, true, basePath + "system", "system messages for this galaxy"); - createRoom(systemAvatar, true, basePath + "Chat", "public chat for this galaxy, can create rooms here"); - createRoom(systemAvatar, true, basePath + "Imperial", "Imperial chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "ImperialWarRoom", "Imperial war room chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Rebel", "Rebel chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "RebelWarRoom", "Rebel war room chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "BountyHunter", "Bounty Hunter chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Commando", "Commando chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Entertainer", "Entertainer chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "ForceSensitive", "Force Sensitive chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Medic", "Medic chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Officer", "Officer chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Pilot", "Pilot chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Politician", "Politician chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Smuggler", "Smuggler chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Spy", "Spy chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Trader", "Trader chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "BeastMastery", "Beast Mastery chat for this galaxy"); - createRoom(systemAvatar, true, basePath + "Auction", "Auction chat for this galaxy"); - createPlanetChannels(systemAvatar, basePath); - } - - private void createPlanetChannels(ChatAvatar systemAvatar, String basePath) { - for (Terrain terrain : terrains) { - String path = basePath + terrain.getNameCapitalized() + "."; - createRoom(systemAvatar, true, path + "Planet", "public chat for this planet, cannot create rooms here"); - createRoom(systemAvatar, true, path + "system", "system messages for this planet, cannot create rooms here"); - createRoom(systemAvatar, true, path + "Chat", "public chat for this planet, can create rooms here"); - } - } - - private boolean incrementMessageCounter(long networkId, int roomId, int messageId) { - if (networkId == -1) // System ChatAvatar uses -1 for networkId - return true; - - Map messageHistory = messages.get(networkId); - if (messageHistory != null) { - if (messageHistory.containsKey(roomId) && messageHistory.get(roomId) == messageId) - return false; - } else { - messageHistory = new HashMap<>(); - messages.put(networkId, messageHistory); - } - messageHistory.put(roomId, messageId); - return true; - } - public void initializeGalaxyChannels(Galaxy galaxy) { - createSystemChannels(galaxy.getName()); - // TODO: Load up persistent channels - /** Channel Notes - * Group channels: created by System - * - SWG.serverName.group.GroupObjectId.GroupChat title = GroupId - * Guild channels: created by System - * - SWG.serverName.guild.GuildId.GuildChat title = GuildId - * City channels: created by System - * - SWG.serverName.city.CityId.CityChat title = CityId - */ - } - - public ChatRoom getRoom(String path) { - for (ChatRoom room : roomMap.values()) { - if (room.getPath().equals(path)) - return room; - } - return null; - } - - public ChatRoom getRoom(int roomId) { - return roomMap.get(roomId); + roomService.createSystemChannels(galaxy.getName()); } /* Friends List */ diff --git a/src/services/chat/ChatRoomService.java b/src/services/chat/ChatRoomService.java new file mode 100644 index 000000000..16a97b2c2 --- /dev/null +++ b/src/services/chat/ChatRoomService.java @@ -0,0 +1,458 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package services.chat; + +import intents.chat.ChatRoomUpdateIntent; +import intents.network.GalacticPacketIntent; +import network.packets.Packet; +import network.packets.swg.SWGPacket; +import network.packets.swg.zone.chat.ChatCreateRoom; +import network.packets.swg.zone.chat.ChatEnterRoomById; +import network.packets.swg.zone.chat.ChatOnCreateRoom; +import network.packets.swg.zone.chat.ChatOnEnteredRoom; +import network.packets.swg.zone.chat.ChatOnLeaveRoom; +import network.packets.swg.zone.chat.ChatOnSendRoomMessage; +import network.packets.swg.zone.chat.ChatQueryRoom; +import network.packets.swg.zone.chat.ChatQueryRoomResults; +import network.packets.swg.zone.chat.ChatRemoveAvatarFromRoom; +import network.packets.swg.zone.chat.ChatRequestRoomList; +import network.packets.swg.zone.chat.ChatSendToRoom; +import network.packets.swg.zone.insertion.ChatRoomList; +import resources.Terrain; +import resources.chat.ChatAvatar; +import resources.chat.ChatResult; +import resources.chat.ChatRoom; +import resources.control.Intent; +import resources.control.Service; +import resources.objects.player.PlayerObject; +import resources.player.AccessLevel; +import resources.player.Player; +import services.player.PlayerManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Waverunner + */ +public class ChatRoomService extends Service { + // Array of only the valid terrains in relation to zones able to use planetary-based chat + private static final List terrains = Arrays.asList( + Terrain.CORELLIA, Terrain.DANTOOINE, Terrain.DATHOMIR, Terrain.ENDOR, Terrain.KASHYYYK, Terrain.LOK, + Terrain.MUSTAFAR, Terrain.NABOO, Terrain.RORI, Terrain.TATOOINE, Terrain.TALUS, Terrain.YAVIN4 + ); + private int maxChatRoomId; + private Map roomMap; + // Map to keep track of each player's recent message for a room to prevent duplicates from client + private Map> messages; + + public ChatRoomService() { + roomMap = new ConcurrentHashMap<>(); + messages = new ConcurrentHashMap<>(); + maxChatRoomId = 1; + } + + @Override + public boolean initialize() { + // TODO: Load up persistent channels + registerForIntent(ChatRoomUpdateIntent.TYPE); + registerForIntent(GalacticPacketIntent.TYPE); + + return super.initialize(); + } + + @Override + public void onIntentReceived(Intent i) { + switch(i.getType()) { + case ChatRoomUpdateIntent.TYPE: + processChatRoomUpdateIntent((ChatRoomUpdateIntent) i); + break; + case GalacticPacketIntent.TYPE: + processPacket((GalacticPacketIntent) i); + break; + } + } + + private void processPacket(GalacticPacketIntent intent) { + Player player = intent.getPlayerManager().getPlayerFromNetworkId(intent.getNetworkId()); + if (player == null) + return; + + Packet p = intent.getPacket(); + if (p instanceof SWGPacket) + processSwgPacket(player, (SWGPacket) p); + } + + private void processSwgPacket(Player player, SWGPacket p) { + switch (p.getPacketType()) { + case CHAT_QUERY_ROOM: + if (p instanceof ChatQueryRoom) handleChatQueryRoom(player, (ChatQueryRoom) p); + break; + case CHAT_ENTER_ROOM_BY_ID: { + if (!(p instanceof ChatEnterRoomById)) return; + ChatEnterRoomById enterRoomById = (ChatEnterRoomById) p; + enterChatChannel(player, enterRoomById.getRoomId(), enterRoomById.getSequence()); + break; } + case CHAT_REMOVE_AVATAR_FROM_ROOM: + if (p instanceof ChatRemoveAvatarFromRoom) + leaveChatChannel(player, ((ChatRemoveAvatarFromRoom) p).getPath()); + break; + case CHAT_SEND_TO_ROOM: + if (p instanceof ChatSendToRoom) handleChatSendToRoom(player, (ChatSendToRoom) p); + break; + case CHAT_REQUEST_ROOM_LIST: + if (p instanceof ChatRequestRoomList) + handleChatRoomListRequest(player); + break; + case CHAT_CREATE_ROOM: + if (p instanceof ChatCreateRoom) handleChatCreateRoom(player, (ChatCreateRoom) p); + break; + } + } + + private void processChatRoomUpdateIntent(ChatRoomUpdateIntent i) { + switch(i.getUpdateType()) { + case CREATE: createRoom(i.getAvatar(), i.isPublic(), i.getPath(), i.getTitle()); break; + default: break; + } + } + + /* Chat Rooms */ + + private void handleChatCreateRoom(Player player, ChatCreateRoom p) { + String path = p.getRoomName(); + String title = p.getRoomTitle(); + ChatRoom room = getRoom(path); + + ChatResult result = ChatResult.SUCCESS; + if (room != null) + result = ChatResult.ROOM_ALREADY_EXISTS; + + if (result == ChatResult.SUCCESS) { + room = createRoom(ChatAvatar.getFromPlayer(player), p.isPublic(), path, title); + room.setMuted(p.isModerated()); + } + + player.sendPacket(new ChatOnCreateRoom(result.getCode(), room, p.getSequence())); + } + + private void handleChatSendToRoom(Player player, ChatSendToRoom p) { + ChatResult result = ChatResult.SUCCESS; + + ChatRoom room = getRoom(p.getRoomId()); + if (room == null) + result = ChatResult.ROOM_INVALID_ID; + + if (result != ChatResult.SUCCESS) { + player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence())); + return; + } + + if (!incrementMessageCounter(player.getNetworkId(), room.getId(), p.getSequence())) + return; + + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + result = room.canSendMessage(avatar); + + // TODO: Check length of messages -- Result 16 is used for too long message + + player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence())); + + if (result == ChatResult.SUCCESS) + room.sendMessage(avatar, p.getMessage(), p.getOutOfBandPackage(), player.getPlayerManager()); + } + + private void handleChatQueryRoom(Player player, ChatQueryRoom p) { + ChatRoom room = getRoom(p.getRoomPath()); // No result code is sent for queries + if (room == null) + return; + + player.sendPacket(new ChatQueryRoomResults(room, p.getSequence())); + } + + private void handleChatRoomListRequest(Player player) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + List rooms = new ArrayList<>(); + for (ChatRoom chatRoom : roomMap.values()) { + if (!chatRoom.isPublic() && !chatRoom.getInvited().contains(avatar) && !chatRoom.getOwner().equals(avatar)) + continue; + rooms.add(chatRoom); + } + + ChatRoomList response = new ChatRoomList(rooms); + player.sendPacket(response); + } + + private void sendOnEnteredChatRoom(Player player, ChatAvatar avatar, ChatResult result, int id, int sequence) { + ChatOnEnteredRoom onEnteredRoom = new ChatOnEnteredRoom(avatar, id, sequence); + onEnteredRoom.setResult(result.getCode()); + player.sendPacket(onEnteredRoom); + } + + public void enterChatChannels(Player player, List channels) { + for (String s : channels) { + enterChatChannel(player, s); + } + } + + public void enterPlanetaryChatChannels(Player player) { + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; + + // Leave old zone-only chat channels + String planetEndPath = ".Planet"; + for (String channel : ghost.getJoinedChannels()) { + if (channel.endsWith(planetEndPath)) { + leaveChatChannel(player, channel); + } else { + // Better way of doing this? + String[] split = channel.split("\\."); + if (split.length == 3 && split[2].equals("system")) + leaveChatChannel(player, channel); + } + } + + Terrain terrain = player.getCreatureObject().getLocation().getTerrain(); + + // Enter the new zone-only chat channels + if (!terrains.contains(terrain)) + return; + + String planetPath = "SWG." + player.getGalaxyName() + "." + terrain.getNameCapitalized() + "."; + enterChatChannel(player, planetPath + "Planet"); + enterChatChannel(player, planetPath + "system"); + } + + /** + * Attempts to join the specified chat channel + * @param player Player joining the chat channel + * @param room Chat room to enter + */ + public void enterChatChannel(Player player, ChatRoom room, int sequence) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) { + sendOnEnteredChatRoom(player, avatar, ChatResult.NONE, room.getId(), sequence); + return; + } + + ChatResult result = room.canJoinRoom(avatar); + if (result != ChatResult.SUCCESS && player.getAccessLevel() != AccessLevel.PLAYER) { + sendOnEnteredChatRoom(player, avatar, result, room.getId(), sequence); + return; + } + // TODO: Check if player is appropriate faction for the room (Rebel and imperial chat rooms) + + // Server-based list so we can join chat channels automatically + ghost.addJoinedChannel(room.getPath()); + + // Re-send the player the room list with just this room as it could have been private/hidden + // This also "refreshes" the client, not sending this will cause a Chat channel unavailable message. + ChatRoomList roomList = new ChatRoomList(room); + player.sendPacket(roomList); + + // Notify players of success, it's ChatResult.SUCCESS at this point + player.sendPacket(new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), sequence)); + + PlayerManager manager = player.getPlayerManager(); + // Notify everyone that a player entered the room + room.sendPacketToMembers(manager, new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), 0)); + + room.getMembers().add(avatar); + } + + public void enterChatChannel(Player player, int id, int sequence) { + ChatRoom room = getRoom(id); + if (room == null) { + sendOnEnteredChatRoom(player, ChatAvatar.getFromPlayer(player), ChatResult.NONE, id, sequence); + return; + } + enterChatChannel(player, room, sequence); + } + + public void enterChatChannel(Player player, String path) { + for (ChatRoom room : roomMap.values()) { + if (room.getPath().equals(path)) { + enterChatChannel(player, room, 0); + return; + } + } + System.err.println("Tried to join a room with a path that does not exist: " + path); + } + + public void leaveChatChannel(Player player, ChatRoom room, int sequence) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; // ChatOnLeaveRoom doesn't do anything other than for a ChatResult.SUCCESS, so no need to send a fail + + if (!room.getMembers().remove(avatar) && !ghost.removeJoinedChannel(room.getPath())) + return; + + player.sendPacket(new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), sequence)); + + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), 0)); + } + + public void leaveChatChannel(Player player, String path) { + for (ChatRoom chatRoom : roomMap.values()) { + if (chatRoom.getPath().equals(path)) + leaveChatChannel(player, chatRoom, 0); + } + } + /** + * Creates a new, non-persistent, chat room with the specified address path. + * @param creator Room creator who will also become the owner of this room + * @param isPublic Determines if the room should be publicly displayed in the channel listing + * @param path Address for the channel (Ex: SWG.serverName.Imperial) + * @param title Descriptive name of the chat channel (Ex: Imperial chat for this galaxy) + * @return {@link ChatRoom} + */ + public ChatRoom createRoom(ChatAvatar creator, boolean isPublic, String path, String title) { + if (path.isEmpty() || path.endsWith(".")) + return null; + + String base = "SWG." + creator.getGalaxy(); + if (!path.startsWith(base) || path.equals(base)) + return null; + + // All paths should have parents, lets validate to make sure they exist first. Create them if they don't. + int lastIndex = path.lastIndexOf("."); + if (lastIndex != -1) { + String parentPath = path.substring(0, lastIndex); + if (getRoom(parentPath) == null) { + createRoom(creator, isPublic, parentPath, ""); + } + } + + ChatRoom room = new ChatRoom(); + int id = maxChatRoomId; + maxChatRoomId++; + + room.setId(id); + room.setOwner(creator); + room.setCreator(creator); + room.setIsPublic(isPublic); + room.setPath(path); + room.setTitle(title); + + roomMap.put(id, room); + return room; + } + + public boolean destroyRoom(ChatRoom room) { + // Remove existing, online, members from the room + // Remove room from map and database + // Remove roompath from joinedChannels list if player tried to join an invalid room + return false; + } + + public void createSystemChannels(String galaxy) { + /** Channel Notes + * Group channels: created by System + * - SWG.serverName.group.GroupObjectId.GroupChat title = GroupId + * Guild channels: created by System + * - SWG.serverName.guild.GuildId.GuildChat title = GuildId + * City channels: created by System + * - SWG.serverName.city.CityId.CityChat title = CityId + */ + + ChatAvatar systemAvatar = ChatAvatar.getSystemAvatar(galaxy); + // TODO: Move to server datatable file (SDF): Room (s) | Title (s) | Private (b) + String basePath = "SWG." + galaxy + "."; + createRoom(systemAvatar, true, basePath + "Galaxy", "public chat for the whole galaxy, cannot create rooms here"); + createRoom(systemAvatar, true, basePath + "system", "system messages for this galaxy"); + createRoom(systemAvatar, true, basePath + "Chat", "public chat for this galaxy, can create rooms here"); + createRoom(systemAvatar, true, basePath + "Imperial", "Imperial chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "ImperialWarRoom", "Imperial war room chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Rebel", "Rebel chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "RebelWarRoom", "Rebel war room chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "BountyHunter", "Bounty Hunter chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Commando", "Commando chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Entertainer", "Entertainer chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "ForceSensitive", "Force Sensitive chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Medic", "Medic chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Officer", "Officer chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Pilot", "Pilot chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Politician", "Politician chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Smuggler", "Smuggler chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Spy", "Spy chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Trader", "Trader chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "BeastMastery", "Beast Mastery chat for this galaxy"); + createRoom(systemAvatar, true, basePath + "Auction", "Auction chat for this galaxy"); + createPlanetChannels(systemAvatar, basePath); + } + + private void createPlanetChannels(ChatAvatar systemAvatar, String basePath) { + // TODO: Move planets to a server datatable file (SDF): Planet (s) + for (Terrain terrain : terrains) { + String path = basePath + terrain.getNameCapitalized() + "."; + createRoom(systemAvatar, true, path + "Planet", "public chat for this planet, cannot create rooms here"); + createRoom(systemAvatar, true, path + "system", "system messages for this planet, cannot create rooms here"); + createRoom(systemAvatar, true, path + "Chat", "public chat for this planet, can create rooms here"); + } + } + + private boolean incrementMessageCounter(long networkId, int roomId, int messageId) { + if (networkId == -1) // System ChatAvatar uses -1 for networkId + return true; + + Map messageHistory = messages.get(networkId); + if (messageHistory != null) { + if (messageHistory.containsKey(roomId) && messageHistory.get(roomId) == messageId) + return false; + } else { + messageHistory = new HashMap<>(); + messages.put(networkId, messageHistory); + } + messageHistory.put(roomId, messageId); + return true; + } + + public ChatRoom getRoom(String path) { + for (ChatRoom room : roomMap.values()) { + if (room.getPath().equals(path)) + return room; + } + return null; + } + + public ChatRoom getRoom(int roomId) { + return roomMap.get(roomId); + } + +} diff --git a/src/services/galaxy/GalacticManager.java b/src/services/galaxy/GalacticManager.java index ac78b52d0..cdc454208 100644 --- a/src/services/galaxy/GalacticManager.java +++ b/src/services/galaxy/GalacticManager.java @@ -33,7 +33,7 @@ import intents.network.InboundPacketIntent; import resources.Galaxy; import resources.control.Intent; import resources.control.Manager; -import services.chat.ChatService; +import services.chat.ChatManager; import services.objects.ObjectManager; import services.player.PlayerManager; @@ -44,7 +44,7 @@ public class GalacticManager extends Manager { private ObjectManager objectManager; private PlayerManager playerManager; private GameManager gameManager; - private ChatService chatService; + private ChatManager chatManager; private Intent prevPacketIntent; private Galaxy galaxy; @@ -53,19 +53,19 @@ public class GalacticManager extends Manager { objectManager = new ObjectManager(); playerManager = new PlayerManager(); gameManager = new GameManager(); - chatService = new ChatService(); + chatManager = new ChatManager(); prevPacketIntent = null; addChildService(objectManager); addChildService(playerManager); addChildService(gameManager); - addChildService(chatService); + addChildService(chatManager); } @Override public boolean initialize() { registerForIntent(InboundPacketIntent.TYPE); - chatService.initializeGalaxyChannels(galaxy); + chatManager.initializeGalaxyChannels(galaxy); return super.initialize(); } diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 5b29af5a7..8567358e0 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -28,7 +28,6 @@ package services.galaxy; import resources.control.Manager; -import services.chat.ChatService; import services.commands.CommandService; import services.sui.SuiService; diff --git a/src/services/player/PlayerManager.java b/src/services/player/PlayerManager.java index 7c3d466ff..9b9af40c2 100644 --- a/src/services/player/PlayerManager.java +++ b/src/services/player/PlayerManager.java @@ -31,6 +31,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -149,7 +150,61 @@ public class PlayerManager extends Manager { } } } - + + public void notifyPlayers(NotifyPlayersPacketIntent.ConditionalNotify conditional, Packet... packets) { + if (conditional == null) { + notifyPlayers(packets); + return; + } + + synchronized (players) { + for (Player player : players.values()) { + if (conditional.meetsCondition(player)) { + player.sendPacket(packets); + } + } + } + } + + public void notifyPlayers(List networkIds, NotifyPlayersPacketIntent.ConditionalNotify conditionalNotify, Packet... packets) { + if (conditionalNotify == null) { + notifyPlayers(networkIds, packets); + return; + } + + synchronized (players) { + networkIds.forEach(id -> { + Player p = players.get(id); + if (p != null && p.getCreatureObject() != null && conditionalNotify.meetsCondition(p)) + p.sendPacket(packets); + }); + } + } + + public void notifyPlayers(List networkIds, Packet... packets) { + synchronized (players) { + networkIds.forEach(id -> { + Player p = players.get(id); + if (p != null && p.getCreatureObject() != null) + p.sendPacket(packets); + }); + } + } + + public void notifyPlayersAtPlanet(NotifyPlayersPacketIntent.ConditionalNotify conditional, Terrain terrain, Packet... packets) { + if (conditional == null) { + notifyPlayersAtPlanet(terrain, packets); + return; + } + + synchronized(players) { + for (Player p : players.values()) { + if (p != null && p.getCreatureObject() != null && p.getCreatureObject().getLocation().getTerrain() == terrain && conditional.meetsCondition(p)) + p.sendPacket(packets); + } + } + } + public void notifyPlayersAtPlanet(Terrain terrain, Packet... packets) { synchronized(players) { for (Player p : players.values()) { @@ -159,13 +214,28 @@ public class PlayerManager extends Manager { } } - public void notifyPlayersWithCondition(NotifyPlayersPacketIntent.ConditionalNotify conditional, Packet... packets) { - synchronized (players) { - for (Player player : players.values()) { - if (conditional.meetsCondition(player)) { - player.sendPacket(packets); - } - } + public void notifyPlayersAtPlanet(List networkIds, NotifyPlayersPacketIntent.ConditionalNotify conditional, Terrain terrain, Packet... packets) { + if(conditional == null) { + notifyPlayersAtPlanet(networkIds, terrain, packets); + return; + } + + synchronized(players) { + networkIds.forEach(id -> { + Player p = players.get(id); + if (p != null && p.getCreatureObject() != null && p.getCreatureObject().getLocation().getTerrain() == terrain && conditional.meetsCondition(p)) + p.sendPacket(packets); + }); + } + } + + public void notifyPlayersAtPlanet(List networkIds, Terrain terrain, Packet... packets) { + synchronized(players) { + networkIds.forEach(id -> { + Player p = players.get(id); + if (p != null && p.getCreatureObject() != null && p.getCreatureObject().getLocation().getTerrain() == terrain) + p.sendPacket(packets); + }); } } @@ -260,12 +330,12 @@ public class PlayerManager extends Manager { } private void onNotifyPlayersPacketIntent(NotifyPlayersPacketIntent nppi) { - if (nppi.getCondition() != null) { - notifyPlayersWithCondition(nppi.getCondition(), nppi.getPacket()); - } else if (nppi.getTerrain() != null) { - notifyPlayersAtPlanet(nppi.getTerrain(), nppi.getPacket()); + if (nppi.getNetworkIds() != null) { + if (nppi.getTerrain() != null) notifyPlayersAtPlanet(nppi.getNetworkIds(), nppi.getCondition(), nppi.getTerrain(), nppi.getPacket()); + else notifyPlayers(nppi.getNetworkIds(), nppi.getCondition(), nppi.getPacket()); } else { - notifyPlayers(nppi.getPacket()); + if (nppi.getTerrain() != null) notifyPlayersAtPlanet(nppi.getCondition(), nppi.getTerrain(), nppi.getPacket()); + else notifyPlayers(nppi.getCondition(), nppi.getPacket()); } } }