diff --git a/src/intents/chat/ChatBroadcastIntent.java b/src/intents/chat/ChatBroadcastIntent.java index 86afae761..f5ac2592c 100644 --- a/src/intents/chat/ChatBroadcastIntent.java +++ b/src/intents/chat/ChatBroadcastIntent.java @@ -33,6 +33,7 @@ import resources.encodables.ProsePackage; import resources.player.Player; public class ChatBroadcastIntent extends Intent { + public static final String TYPE = "ChatBroadcastIntent"; private BroadcastType broadcastType; @@ -53,26 +54,36 @@ public class ChatBroadcastIntent extends Intent { this(null, receiver, null, BroadcastType.PERSONAL); this.prose = prose; } + public ChatBroadcastIntent(String message, BroadcastType type) { this(message, null, null, type); } + public ChatBroadcastIntent(Player receiver, String message) { this(message, receiver, null, BroadcastType.PERSONAL); } - public ChatBroadcastIntent(String message) { - this(message, null, null, BroadcastType.GALAXY); + + public BroadcastType getBroadcastType() { + return broadcastType; } - public BroadcastType getBroadcastType() { return broadcastType; } - public Player getBroadcaster() { return broadcaster; } - public Terrain getTerrain() { return terrain; } - public String getMessage() { return message; } - public ProsePackage getProse() { return prose; } + public Player getBroadcaster() { + return broadcaster; + } + + public Terrain getTerrain() { + return terrain; + } + + public String getMessage() { + return message; + } + + public ProsePackage getProse() { + return prose; + } public enum BroadcastType { - AREA, - PLANET, - GALAXY, - PERSONAL + AREA, PLANET, GALAXY, PERSONAL } } diff --git a/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java b/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java index ab128a577..90fe54dfe 100644 --- a/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java +++ b/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java @@ -29,60 +29,52 @@ package network.packets.swg.zone.chat; import network.packets.swg.SWGPacket; import resources.chat.ChatAvatar; +import resources.chat.ChatResult; import java.nio.ByteBuffer; public class ChatOnEnteredRoom extends SWGPacket { + public static final int CRC = getCrc("ChatOnEnteredRoom"); - + private ChatAvatar avatar; - private int result; + private ChatResult result; private int chatRoomId; private int sequence; - - public ChatOnEnteredRoom() { - - } public ChatOnEnteredRoom(ChatAvatar avatar, int chatRoomId, int sequence) { - this.avatar = avatar; - this.chatRoomId = chatRoomId; - this.sequence = sequence; + this(avatar, ChatResult.NONE, chatRoomId, sequence); } - - public ChatOnEnteredRoom(ChatAvatar avatar, int result, int chatRoomId, int sequence) { + + public ChatOnEnteredRoom(ChatAvatar avatar, ChatResult result, int chatRoomId, int sequence) { this.avatar = avatar; this.result = result; this.chatRoomId = chatRoomId; this.sequence = sequence; } - + public ChatOnEnteredRoom(ByteBuffer data) { decode(data); } - + public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - avatar = getEncodable(data, ChatAvatar.class); - result = getInt(data); - chatRoomId = getInt(data); - sequence = getInt(data); + avatar = getEncodable(data, ChatAvatar.class); + result = ChatResult.fromInteger(getInt(data)); + chatRoomId = getInt(data); + sequence = getInt(data); } public ByteBuffer encode() { ByteBuffer data = ByteBuffer.allocate(avatar.getSize() + 18); addShort(data, 5); - addInt (data, CRC); + addInt(data, CRC); addEncodable(data, avatar); - addInt(data, result); - addInt (data, chatRoomId); - addInt (data, sequence); + addInt(data, result.getCode()); + addInt(data, chatRoomId); + addInt(data, sequence); return data; } - - public void setResult(int result) { - this.result = result; - } + } - diff --git a/src/resources/chat/ChatAvatar.java b/src/resources/chat/ChatAvatar.java index 46fb5c787..1c0362b01 100644 --- a/src/resources/chat/ChatAvatar.java +++ b/src/resources/chat/ChatAvatar.java @@ -26,160 +26,121 @@ ******************************************************************************/ package resources.chat; -import network.packets.Packet; +import resources.encodables.CachedEncode; import resources.encodables.Encodable; +import resources.network.NetBuffer; import resources.network.NetBufferStream; import resources.persistable.Persistable; import resources.player.Player; -import java.io.IOException; -import java.io.ObjectInputStream; import java.nio.ByteBuffer; import java.util.Locale; + import services.CoreManager; -/** - * @author Waverunner - */ public class ChatAvatar implements Encodable, Persistable { - private long networkId; + private final CachedEncode cache; + + private Player player; private String name; - private String game = "SWG"; - private String galaxy; - - // Instances of the class will hardly ever change, so we can just pre-build the data. - private transient byte[] data; - private transient boolean modified; - private transient int size = 0; - + public ChatAvatar() { - modified = true; - data = null; - size = 0; - } - - public ChatAvatar(long networkId, String name, String galaxy) { - this(); - this.networkId = networkId; - this.name = name; - this.galaxy = galaxy; + this(null, null); } - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - modified = true; - data = null; - size = 0; + public ChatAvatar(Player player, String name) { + this.cache = new CachedEncode(() -> encodeImpl()); + this.player = player; + this.name = name; } - + public String getName() { return name; } - + public void setName(String name) { this.name = name; - modified = true; + this.cache.clearCached(); } - - public String getGame() { - return game; - } - - public void setGame(String game) { - this.game = game; - modified = true; - } - + public String getGalaxy() { - return galaxy; + return getCoreGalaxy(); } - - public void setGalaxy(String galaxy) { - this.galaxy = galaxy; - modified = true; + + public Player getPlayer() { + return player; } - - public long getNetworkId() { - return networkId; + + public void setPlayer(Player player) { + this.player = player; } - + + private byte [] encodeImpl() { + NetBuffer buffer = NetBuffer.allocate(getSize()); + buffer.addAscii("SWG"); + buffer.addAscii(getCoreGalaxy()); + buffer.addAscii(name); + return buffer.array(); + } + public int getSize() { - if (size == 0) - size = 6 + game.length() + name.length() + galaxy.length(); - - return size; + return 9 + name.length() + getCoreGalaxy().length(); } - + @Override public byte[] encode() { - if (!modified && data != null) - return data; - - ByteBuffer buffer = ByteBuffer.allocate(getSize()); - Packet.addAscii(buffer, game); - Packet.addAscii(buffer, galaxy); - Packet.addAscii(buffer, name); - - data = buffer.array(); - modified = false; - - return data; + return cache.encode(); } - + @Override - public void decode(ByteBuffer data) { - game = Packet.getAscii(data); - galaxy = Packet.getAscii(data); - name = Packet.getAscii(data).toLowerCase(Locale.US); + public void decode(ByteBuffer bb) { + NetBuffer data = NetBuffer.wrap(bb); + data.getAscii(); // SWG + data.getAscii(); + name = data.getAscii().toLowerCase(Locale.US); } @Override public void save(NetBufferStream stream) { - stream.addByte(0); - stream.addAscii(galaxy); + stream.addByte(1); stream.addAscii(name); } @Override public void read(NetBufferStream stream) { - stream.getByte(); - galaxy = stream.getAscii(); + byte ver = stream.getByte(); + if (ver == 0) + stream.getAscii(); name = stream.getAscii(); } @Override public String toString() { - return "ChatAvatar[networkId=" + networkId + - ", name='" + name + '\'' + ", game='" + game + '\'' + ", galaxy='" + galaxy + "']"; + return String.format("ChatAvatar[name='%s']", name); } - + @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - - if (getClass() != o.getClass()) + if (!(o instanceof ChatAvatar)) return false; - - // NetworkId will be 0 if decoded from a packet - if (networkId != 0) return ((ChatAvatar) o).getNetworkId() == networkId; - else return ((ChatAvatar) o).getName().equals(getName()); + return ((ChatAvatar) o).getName().equals(getName()); } - + @Override public int hashCode() { - // NetworkId will be 0 if decoded from a packet - int result = (networkId != 0 ? (int) (networkId ^ (networkId >>> 32)) : 0); - result = 31 * result + name.hashCode(); - return result; + return name.hashCode(); } - + public static ChatAvatar getFromPlayer(Player player) { - return new ChatAvatar(player.getNetworkId(), player.getCharacterName().split(" ")[0].toLowerCase(Locale.US), CoreManager.getGalaxy().getName()); + return new ChatAvatar(player, player.getCharacterFirstName().toLowerCase(Locale.US)); } - - public static ChatAvatar getSystemAvatar(String galaxy) { - return new ChatAvatar(-1, "system", galaxy); + + public static ChatAvatar getSystemAvatar() { + return new ChatAvatar(null, "system"); + } + + private static String getCoreGalaxy() { + return CoreManager.getGalaxy().getName(); } } diff --git a/src/resources/chat/ChatResult.java b/src/resources/chat/ChatResult.java index fa237e24e..1d836efb9 100644 --- a/src/resources/chat/ChatResult.java +++ b/src/resources/chat/ChatResult.java @@ -27,41 +27,51 @@ package resources.chat; +import java.util.HashMap; +import java.util.Map; + /** * @author Waverunner */ public enum ChatResult { - NONE(-1), // The client will just display an "unknown error code" if this is used. - SUCCESS(0), - TARGET_AVATAR_DOESNT_EXIST(4), - ROOM_INVALID_ID(5), - ROOM_INVALID_NAME(6), - CUSTOM_FAILURE(9), - ROOM_AVATAR_BANNED(12), - ROOM_PRIVATE(13), - ROOM_AVATAR_NO_PERMISSION(16), - IGNORED(23), - ROOM_ALREADY_EXISTS(24), - ROOM_ALREADY_JOINED(36), - CHAT_SERVER_UNAVAILABLE(1000000), - ROOM_DIFFERENT_FACTION(1000001), - ROOM_NOT_GCW_DEFENDER_FACTION(1000005); - - + NONE (-1), // The client will just display an "unknown error code" if this is used. + SUCCESS (0), + TARGET_AVATAR_DOESNT_EXIST (4), + ROOM_INVALID_ID (5), + ROOM_INVALID_NAME (6), + CUSTOM_FAILURE (9), + ROOM_AVATAR_BANNED (12), + ROOM_PRIVATE (13), + ROOM_AVATAR_NO_PERMISSION (16), + IGNORED (23), + ROOM_ALREADY_EXISTS (24), + ROOM_ALREADY_JOINED (36), + CHAT_SERVER_UNAVAILABLE (1000000), + ROOM_DIFFERENT_FACTION (1000001), + ROOM_NOT_GCW_DEFENDER_FACTION (1000005); + + private static final Map RESULT_MAP = new HashMap<>(); + + static { + for (ChatResult result : values()) { + RESULT_MAP.put(result.getCode(), result); + } + } + private final int code; + ChatResult(int code) { this.code = code; } - + public int getCode() { return code; } - + public static ChatResult fromInteger(int code) { - for (ChatResult result : ChatResult.values()) { - if (code == result.getCode()) - return result; - } - return NONE; + ChatResult result = RESULT_MAP.get(code); + if (result == null) + return NONE; + return result; } } diff --git a/src/resources/chat/ChatRoom.java b/src/resources/chat/ChatRoom.java index 7493c4284..21698e0fd 100644 --- a/src/resources/chat/ChatRoom.java +++ b/src/resources/chat/ChatRoom.java @@ -32,9 +32,7 @@ import network.packets.swg.zone.chat.ChatRoomMessage; import resources.encodables.Encodable; import resources.encodables.OutOfBandPackage; import resources.network.NetBufferStream; -import resources.objects.player.PlayerObject; import resources.persistable.Persistable; -import resources.player.Player; import services.player.PlayerManager; import java.io.IOException; @@ -43,9 +41,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -/** - * @author Waverunner - */ public class ChatRoom implements Encodable, Persistable { private int id; @@ -61,7 +56,7 @@ public class ChatRoom implements Encodable, Persistable { // Members are only actually apart of a room when they're "in the room", so we don't need to save this info // as each player will automatically re-join the room based on their joined channels list private transient List members; - + // Large amount of data that's sent frequently, best if we can pre-encode everything and save it for later private transient boolean modified = true; private transient byte[] data; @@ -233,31 +228,17 @@ public class ChatRoom implements Encodable, Persistable { public void sendMessage(ChatAvatar sender, String message, OutOfBandPackage oob, PlayerManager playerManager) { ChatRoomMessage chatRoomMessage = new ChatRoomMessage(sender, getId(), message, oob); - - String senderName = sender.getName(); for (ChatAvatar member : members) { - Player player = playerManager.getPlayerFromNetworkId(member.getNetworkId()); - if (player == null) + if (member.getPlayer().getPlayerObject().isIgnored(sender.getName())) continue; - - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - continue; - - if (ghost.isIgnored(senderName)) - continue; - - player.sendPacket(chatRoomMessage); + + member.getPlayer().sendPacket(chatRoomMessage); } } public void sendPacketToMembers(PlayerManager manager, SWGPacket... packets) { for (ChatAvatar member : members) { - Player player = manager.getPlayerFromNetworkId(member.getNetworkId()); - if (player == null) - continue; - - player.sendPacket(packets); + member.getPlayer().sendPacket(packets); } } diff --git a/src/resources/encodables/CachedEncode.java b/src/resources/encodables/CachedEncode.java new file mode 100644 index 000000000..0523f87f4 --- /dev/null +++ b/src/resources/encodables/CachedEncode.java @@ -0,0 +1,55 @@ +/************************************************************************************ + * 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 resources.encodables; + +public class CachedEncode { + + private final EncodeOnly encodable; + + private byte [] cached; + + public CachedEncode(EncodeOnly encodable) { + this.encodable = encodable; + this.cached = null; + } + + public void clearCached() { + this.cached = null; + } + + public byte[] encode() { + if (cached == null) + cached = encodable.encode(); + return cached; + } + + public interface EncodeOnly { + byte [] encode(); + } + +} diff --git a/src/resources/encodables/Encodable.java b/src/resources/encodables/Encodable.java index 9a497a6ed..c9c551fa5 100644 --- a/src/resources/encodables/Encodable.java +++ b/src/resources/encodables/Encodable.java @@ -29,9 +29,6 @@ package resources.encodables; import java.nio.ByteBuffer; -/** - * @author Waverunner - */ public interface Encodable { byte [] encode(); void decode(ByteBuffer data); diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java index 96ebfe6f7..c61fe359b 100644 --- a/src/resources/objects/player/PlayerObject.java +++ b/src/resources/objects/player/PlayerObject.java @@ -42,7 +42,6 @@ import resources.player.PlayerFlags; import utilities.Encoder.StringType; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class PlayerObject extends IntangibleObject { @@ -279,37 +278,45 @@ public class PlayerObject extends IntangibleObject { sendDelta(8, 6, activeQuest); } - public void removeFriend(String friend) { - play9.removeFriend(friend, this); + public boolean addFriend(String friend) { + return play9.addFriend(friend, this); } - public void addFriend(String friend) { - play9.addFriend(friend, this); + public boolean removeFriend(String friend) { + return play9.removeFriend(friend, this); } public boolean isFriend(String target) { return play9.isFriend(target); } - + public List getFriendsList() { return play9.getFriendsList(); } - - public void addIgnored(String ignored) { - play9.addIgnored(ignored, this); + + public void sendFriendsList() { + play9.sendFriendsList(this); } - public void removeIgnored(String ignored) { - play9.removeIgnored(ignored, this); + public boolean addIgnored(String ignored) { + return play9.addIgnored(ignored, this); + } + + public boolean removeIgnored(String ignored) { + return play9.removeIgnored(ignored, this); } public boolean isIgnored(String target) { return play9.isIgnored(target); } - + public List getIgnoreList() { return play9.getIgnoreList(); } + + public void sendIgnoreList() { + play9.sendIgnoreList(this); + } public String getProfWheelPosition() { return play8.getProfWheelPosition(); @@ -333,7 +340,7 @@ public class PlayerObject extends IntangibleObject { } public List getJoinedChannels() { - return Collections.unmodifiableList(joinedChannels); + return new ArrayList<>(joinedChannels); } public boolean addJoinedChannel(String path) { diff --git a/src/resources/objects/player/PlayerObjectPrivateNP.java b/src/resources/objects/player/PlayerObjectPrivateNP.java index 2e7611f2f..973a306e6 100644 --- a/src/resources/objects/player/PlayerObjectPrivateNP.java +++ b/src/resources/objects/player/PlayerObjectPrivateNP.java @@ -27,7 +27,9 @@ ***********************************************************************************/ package resources.objects.player; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; import resources.collections.SWGList; import resources.collections.SWGSet; @@ -58,50 +60,70 @@ class PlayerObjectPrivateNP implements Persistable { } - public void removeFriend(String friend, SWGObject target) { - synchronized (friendsList) { - friendsList.remove(friend); - } - friendsList.sendDeltaMessage(target); - } - - public void addFriend(String friend, SWGObject target) { + public boolean addFriend(String friend, SWGObject target) { + friend = friend.toLowerCase(Locale.US); synchronized (friendsList) { + if (friendsList.contains(friend)) + return false; friendsList.add(friend); } friendsList.sendDeltaMessage(target); + return true; + } + + public boolean removeFriend(String friend, SWGObject target) { + boolean changed; + synchronized (friendsList) { + changed = friendsList.remove(friend.toLowerCase(Locale.US)); + } + friendsList.sendDeltaMessage(target); + return changed; } public boolean isFriend(String friend) { synchronized (friendsList) { - return friendsList.contains(friend); + return friendsList.contains(friend.toLowerCase(Locale.US)); } } public List getFriendsList() { - return friendsList; + return new ArrayList<>(friendsList); } - public void addIgnored(String ignored, SWGObject target) { + public void sendFriendsList(SWGObject target) { + friendsList.sendRefreshedListData(target); + } + + public boolean addIgnored(String ignored, SWGObject target) { + ignored = ignored.toLowerCase(Locale.US); synchronized (ignoreList) { + if (ignoreList.contains(ignored)) + return false; ignoreList.add(ignored); } ignoreList.sendDeltaMessage(target); + return true; } - public void removeIgnored(String ignored, SWGObject target) { + public boolean removeIgnored(String ignored, SWGObject target) { + boolean changed; synchronized (ignoreList) { - ignoreList.remove(ignored); + changed = ignoreList.remove(ignored.toLowerCase(Locale.US)); } ignoreList.sendDeltaMessage(target); + return changed; } public boolean isIgnored(String target) { - return ignoreList.contains(target); + return ignoreList.contains(target.toLowerCase(Locale.US)); } public List getIgnoreList() { - return ignoreList; + return new ArrayList<>(ignoreList); + } + + public void sendIgnoreList(SWGObject target) { + ignoreList.sendRefreshedListData(target); } public void addDraftSchematic(String schematic, SWGObject target) { diff --git a/src/resources/player/Player.java b/src/resources/player/Player.java index 4a7f8d1ab..3f7ead461 100644 --- a/src/resources/player/Player.java +++ b/src/resources/player/Player.java @@ -125,6 +125,14 @@ public class Player implements Comparable { return ""; } + public String getCharacterFirstName() { + String name = getCharacterName(); + int spaceIndex = name.indexOf(' '); + if (spaceIndex != -1) + return name.substring(0, spaceIndex); + return name; + } + public int getUserId() { return userId; } diff --git a/src/services/chat/ChatManager.java b/src/services/chat/ChatManager.java index 13f19555b..cb009b44e 100644 --- a/src/services/chat/ChatManager.java +++ b/src/services/chat/ChatManager.java @@ -49,7 +49,7 @@ import network.packets.swg.zone.object_controller.SpatialChat; import resources.Terrain; import resources.chat.ChatAvatar; import resources.chat.ChatResult; -import resources.collections.SWGList; +import resources.control.Assert; import resources.control.Manager; import resources.encodables.OutOfBandPackage; import resources.encodables.ProsePackage; @@ -60,22 +60,20 @@ import resources.player.PlayerState; import resources.server_info.Log; import resources.server_info.RelationalServerData; import resources.server_info.RelationalServerFactory; -import services.CoreManager; -import services.player.PlayerManager; public class ChatManager extends Manager { - + private final ChatRoomService roomService; private final ChatMailService mailService; private final RelationalServerData chatLogs; private final PreparedStatement insertChatLog; - + public ChatManager() { roomService = new ChatRoomService(); mailService = new ChatMailService(); chatLogs = RelationalServerFactory.getServerDatabase("chat/chat_log.db"); insertChatLog = chatLogs.prepareStatement("INSERT INTO chat_log VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - + addChildService(roomService); addChildService(mailService); @@ -90,37 +88,31 @@ public class ChatManager extends Manager { Packet p = gpi.getPacket(); if (!(p instanceof SWGPacket)) return; - SWGPacket swg = (SWGPacket) p; - switch (swg.getPacketType()) { + switch (((SWGPacket) p).getPacketType()) { case CHAT_INSTANT_MESSAGE_TO_CHARACTER: if (p instanceof ChatInstantMessageToCharacter) - handleInstantMessage(gpi.getPlayerManager(), gpi.getPlayer(), (ChatInstantMessageToCharacter) p); + handleInstantMessage(gpi.getPlayer(), (ChatInstantMessageToCharacter) p); break; default: break; } } - + private void handlePlayerEventIntent(PlayerEventIntent pei) { - Player player = pei.getPlayer(); - if (player == null) - return; - switch (pei.getEvent()) { case PE_FIRST_ZONE: - updateChatAvatarStatus(player, true); + updateChatAvatarStatus(pei.getPlayer(), true); break; case PE_LOGGED_OUT: - if (player.getCreatureObject() == null) - break; - updateChatAvatarStatus(player, false); + updateChatAvatarStatus(pei.getPlayer(), false); + break; + default: break; - default: break; } } - + private void handleChatBroadcastIntent(ChatBroadcastIntent cbi) { - switch(cbi.getBroadcastType()) { + switch (cbi.getBroadcastType()) { case AREA: broadcastAreaMessage(cbi.getMessage(), cbi.getBroadcaster()); logChat(cbi.getBroadcaster(), ChatType.SYSTEM, ChatRange.LOCAL, cbi.getMessage()); @@ -134,19 +126,22 @@ public class ChatManager extends Manager { logChat(cbi.getBroadcaster(), ChatType.SYSTEM, ChatRange.GALAXY, cbi.getMessage()); break; case PERSONAL: - broadcastPersonalMessage(cbi.getProse(), cbi.getBroadcaster(), cbi.getMessage()); - logChat(cbi.getBroadcaster(), ChatType.SYSTEM, ChatRange.PERSONAL, cbi.getMessage()); + if (cbi.getProse() != null) { + broadcastPersonalMessage(cbi.getBroadcaster(), cbi.getProse()); + logChat(cbi.getBroadcaster(), ChatType.SYSTEM, ChatRange.PERSONAL, "**OOB DATA**"); + } else { + broadcastPersonalMessage(cbi.getBroadcaster(), cbi.getMessage()); + logChat(cbi.getBroadcaster(), ChatType.SYSTEM, ChatRange.PERSONAL, cbi.getMessage()); + } break; } } - + private void handleChatAvatarRequestIntent(ChatAvatarRequestIntent cari) { switch (cari.getRequestType()) { - case TARGET_STATUS: { - Player player = cari.getPlayer(); - sendTargetAvatarStatus(player, new ChatAvatar(0, cari.getTarget(), CoreManager.getGalaxy().getName())); + case TARGET_STATUS: + sendTargetAvatarStatus(cari.getPlayer(), new ChatAvatar(cari.getPlayer(), cari.getTarget())); break; - } case FRIEND_ADD_TARGET: handleAddFriend(cari.getPlayer(), cari.getTarget()); break; @@ -156,237 +151,183 @@ public class ChatManager extends Manager { case FRIEND_LIST: handleRequestFriendList(cari.getPlayer()); break; - case IGNORE_REMOVE_TARGET: - handleRemoveIgnored(cari.getPlayer(), cari.getTarget()); - break; case IGNORE_ADD_TARGET: handleAddIgnored(cari.getPlayer(), cari.getTarget()); break; + case IGNORE_REMOVE_TARGET: + handleRemoveIgnored(cari.getPlayer(), cari.getTarget()); + break; case IGNORE_LIST: handleRequestIgnoreList(cari.getPlayer()); break; } } - + /* Friends List */ - - private void handleRequestFriendList(Player player) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - SWGList friends = (SWGList) ghost.getFriendsList(); - - friends.sendRefreshedListData(ghost); - } - - private void handleRemoveFriend(Player player, String target) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - if (!ghost.isFriend(target)) { - sendSystemMessage(player, "@cmnty:friend_not_found", target); - return; - } - - ghost.removeFriend(target); - - sendSystemMessage(player, "@cmnty:friend_removed", target); - } - + private void handleAddFriend(Player player, String target) { - if (target.equalsIgnoreCase(player.getCharacterName().split(" ")[0])) + if (target.equalsIgnoreCase(player.getCharacterFirstName())) return; - + PlayerObject ghost = player.getPlayerObject(); if (ghost == null) return; - + if (ghost.isIgnored(target)) { sendSystemMessage(player, "@cmnty:friend_fail_is_ignored", target); return; } - - if (ghost.isFriend(target)) { - sendSystemMessage(player, "@cmnty:friend_duplicate", target); - return; - } - + if (!player.getPlayerManager().playerExists(target)) { sendSystemMessage(player, "@cmnty:friend_not_found", target); return; } - - ghost.addFriend(target); - sendSystemMessage(player, "@cmnty:friend_added", target); - - player.sendPacket(new ChatFriendsListUpdate(new ChatAvatar(0, target, CoreManager.getGalaxy().getName()), true)); - } - - /* Ignore List */ - - private void handleAddIgnored(Player player, String target) { - if (target.equalsIgnoreCase(player.getCharacterName().split(" ")[0])) - return; - - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - if (ghost.isIgnored(target)) { - sendSystemMessage(player, "@cmnty:ignore_duplicate", target); + + if (!ghost.addFriend(target)) { + sendSystemMessage(player, "@cmnty:friend_duplicate", target); return; } - + + sendSystemMessage(player, "@cmnty:friend_added", target); + sendTargetAvatarStatus(player, new ChatAvatar(null, target)); + } + + private void handleRemoveFriend(Player player, String target) { + if (!player.getPlayerObject().removeFriend(target)) { + sendSystemMessage(player, "@cmnty:friend_not_found", target); + return; + } + + sendSystemMessage(player, "@cmnty:friend_removed", target); + } + + private void handleRequestFriendList(Player player) { + player.getPlayerObject().sendFriendsList(); + } + + /* Ignore List */ + + private void handleAddIgnored(Player player, String target) { + if (target.equalsIgnoreCase(player.getCharacterFirstName())) + return; + if (!player.getPlayerManager().playerExists(target)) { sendSystemMessage(player, "@cmnty:ignore_not_found", target); return; } - - ghost.addIgnored(target); + + player.getPlayerObject().removeFriend(target); + + if (!player.getPlayerObject().addIgnored(target)) { + sendSystemMessage(player, "@cmnty:ignore_duplicate", target); + return; + } + sendSystemMessage(player, "@cmnty:ignore_added", target); } - + private void handleRemoveIgnored(Player player, String target) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - if (!ghost.isIgnored(target)) { + if (!player.getPlayerObject().removeIgnored(target)) { sendSystemMessage(player, "@cmnty:ignore_not_found", target); return; } - - ghost.removeIgnored(target); - + sendSystemMessage(player, "@cmnty:ignore_removed", target); } - + private void handleRequestIgnoreList(Player player) { - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) - return; - - SWGList ignored = (SWGList) ghost.getIgnoreList(); - - ignored.sendRefreshedListData(ghost); + player.getPlayerObject().sendIgnoreList(); } - + /* Misc */ - + private void sendSystemMessage(Player player, String stringId, String target) { new ChatBroadcastIntent(player, new ProsePackage("StringId", stringId, "TT", target)).broadcast(); } - + private void handleSpatialChatIntent(SpatialChatIntent spi) { Player sender = spi.getPlayer(); SWGObject actor = sender.getCreatureObject(); + String senderName = sender.getCharacterFirstName(); // Send to self SpatialChat message = new SpatialChat(actor.getObjectId(), actor.getObjectId(), 0, spi.getMessage(), (short) spi.getChatType(), (short) spi.getMoodId()); sender.sendPacket(message); logChat(sender, ChatType.SPATIAL, ChatRange.LOCAL, spi.getMessage()); - - String senderName = ChatAvatar.getFromPlayer(sender).getName(); - + // Notify observers of the chat message for (Player owner : actor.getObservers()) { - PlayerObject awareGhost = owner.getPlayerObject(); - if (!awareGhost.isIgnored(senderName)) - owner.sendPacket(new SpatialChat(owner.getCreatureObject().getObjectId(), message)); + if (owner.getPlayerObject().isIgnored(senderName)) + continue; + owner.sendPacket(new SpatialChat(owner.getCreatureObject().getObjectId(), message)); } } - private void handleInstantMessage(PlayerManager playerMgr, Player sender, ChatInstantMessageToCharacter request) { + private void handleInstantMessage(Player sender, ChatInstantMessageToCharacter request) { String strReceiver = request.getCharacter().toLowerCase(Locale.ENGLISH); - String strSender = sender.getCharacterName(); + String strSender = sender.getCharacterFirstName(); - if (strSender.contains(" ")) - strSender = strSender.split(" ")[0]; + Player receiver = sender.getPlayerManager().getPlayerByCreatureFirstName(strReceiver); - Player receiver = playerMgr.getPlayerByCreatureFirstName(strReceiver); - - int errorCode = 0; // 0 = No issue, 4 = "strReceiver is not online" + ChatResult result = ChatResult.SUCCESS; if (receiver == null || receiver.getPlayerState() != PlayerState.ZONED_IN) - errorCode = 4; - - if (errorCode != 4 && receiver.getPlayerObject() != null && receiver.getPlayerObject().isIgnored(strSender)) - errorCode = ChatResult.IGNORED.getCode(); - - sender.sendPacket(new ChatOnSendInstantMessage(errorCode, request.getSequence())); + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + else if (receiver.getPlayerObject().isIgnored(strSender)) + result = ChatResult.IGNORED; - if (errorCode != 0) + sender.sendPacket(new ChatOnSendInstantMessage(result.getCode(), request.getSequence())); + + if (result != ChatResult.SUCCESS) return; receiver.sendPacket(new ChatInstantMessageToClient(request.getGalaxy(), strSender, request.getMessage())); logChat(sender, receiver, ChatType.TELL, request.getMessage()); } - + /* Friends */ - + private void updateChatAvatarStatus(Player player, boolean online) { - PlayerManager playerManager = player.getPlayerManager(); - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - String galaxy = CoreManager.getGalaxy().getName(); - if (online) { - PlayerObject playerObject = player.getPlayerObject(); - if (playerObject != null && playerObject.getFriendsList().size() <= 0) { - for (String friend : playerObject.getFriendsList()) { - sendTargetAvatarStatus(player, new ChatAvatar(0, friend, galaxy)); - } + for (String friend : player.getPlayerObject().getFriendsList()) { + sendTargetAvatarStatus(player, new ChatAvatar(null, friend)); } } - - final ChatFriendsListUpdate update = new ChatFriendsListUpdate(avatar, online); - - playerManager.notifyPlayers(playerNotified -> { + + String playerName = player.getCharacterFirstName().toLowerCase(Locale.US); + player.getPlayerManager().notifyPlayers(playerNotified -> { if (playerNotified.getPlayerState() != PlayerState.ZONED_IN) return false; - - PlayerObject playerObject = playerNotified.getPlayerObject(); - return playerObject != null && playerObject.getFriendsList().contains(update.getFriend().getName()); - - }, update); + + return playerNotified.getPlayerObject().isFriend(playerName); + }, new ChatFriendsListUpdate(new ChatAvatar(player, playerName), online)); } - + private void sendTargetAvatarStatus(Player player, ChatAvatar target) { - PlayerObject object = player.getPlayerObject(); - if (object == null) - return; - Player targetPlayer = player.getPlayerManager().getPlayerByCreatureFirstName(target.getName()); - - boolean online = true; - if (targetPlayer == null || targetPlayer.getPlayerState() != PlayerState.ZONED_IN) - online = false; - - player.sendPacket(new ChatFriendsListUpdate(target, online)); - } - - private void broadcastAreaMessage(String message, Player broadcaster) { - ChatSystemMessage packet = new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT.ordinal(), message); - broadcaster.sendPacket(packet); - broadcaster.getCreatureObject().sendObservers(packet); + player.sendPacket(new ChatFriendsListUpdate(target, targetPlayer != null && targetPlayer.getPlayerState() == PlayerState.ZONED_IN)); } - + + private void broadcastAreaMessage(String message, Player broadcaster) { + broadcaster.getCreatureObject().sendObserversAndSelf(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT.ordinal(), message)); + } + private void broadcastPlanetMessage(String message, Terrain terrain) { ChatSystemMessage packet = new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT.ordinal(), message); - new NotifyPlayersPacketIntent(packet, terrain, null).broadcast(); + new NotifyPlayersPacketIntent(packet, terrain).broadcast(); } - + private void broadcastGalaxyMessage(String message) { ChatSystemMessage packet = new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT.ordinal(), message); new NotifyPlayersPacketIntent(packet).broadcast(); } - private void broadcastPersonalMessage(ProsePackage prose, Player player, String message) { - if (prose != null) - player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, new OutOfBandPackage(prose))); - else - player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, message)); + private void broadcastPersonalMessage(Player player, String message) { + player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, message)); + } + + private void broadcastPersonalMessage(Player player, ProsePackage prose) { + player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, new OutOfBandPackage(prose))); } private void logChat(Player broadcaster, ChatType type, ChatRange range, String message) { @@ -414,8 +355,7 @@ public class ChatManager extends Manager { } private void logChat(long sendId, String sendName, long recvId, String recvName, String type, String range, String room, String subject, String message) { - if (message == null) - return; + Assert.notNull(message, "Message cannot be null!"); try { synchronized (insertChatLog) { insertChatLog.setLong(1, System.currentTimeMillis()); @@ -435,20 +375,12 @@ public class ChatManager extends Manager { } } - public static enum ChatType { - MAIL, - TELL, - SYSTEM, - SPATIAL, - CHAT + public enum ChatType { + MAIL, TELL, SYSTEM, SPATIAL, CHAT } - public static enum ChatRange { - PERSONAL, - ROOM, - LOCAL, - TERRAIN, - GALAXY + public enum ChatRange { + PERSONAL, ROOM, LOCAL, TERRAIN, GALAXY } } diff --git a/src/services/chat/ChatRoomContainer.java b/src/services/chat/ChatRoomContainer.java new file mode 100644 index 000000000..12e130f8c --- /dev/null +++ b/src/services/chat/ChatRoomContainer.java @@ -0,0 +1,125 @@ +/************************************************************************************ + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import resources.chat.ChatRoom; +import resources.control.Assert; +import resources.server_info.Log; + +public class ChatRoomContainer { + + private final Object mapMutex; + private final Map idMap; + private final Map pathMap; + + public ChatRoomContainer() { + this.mapMutex = new Object(); + this.idMap = new HashMap<>(); + this.pathMap = new HashMap<>(); + } + + public boolean addRoom(ChatRoom room) { + Assert.notNull(room, "ChatRoom cannot be null!"); + Assert.test(room.getId() > 0, "ChatRoom ID must be > 0!"); + Assert.notNull(room.getPath(), "Path must not be null!"); + Assert.test(!room.getPath().isEmpty(), "Path must be non-empty!"); + Assert.test(room.getPath().startsWith("SWG."), "Path must start with \"SWG.\"!"); + synchronized (mapMutex) { + ChatRoom overwrittenId = idMap.put(room.getId(), room); + ChatRoom overwrittenPath = pathMap.put(room.getPath(), room); + if (overwrittenId != null || overwrittenPath != null) { + if (overwrittenId != overwrittenPath) + Log.w("Internal maps in ChatRoomContainer are out of sync! ID and Path don't match for room: %s", room); + if (overwrittenId != null) + idMap.put(overwrittenId.getId(), overwrittenId); + else + idMap.put(overwrittenPath.getId(), overwrittenPath); + if (overwrittenPath != null) + pathMap.put(overwrittenPath.getPath(), overwrittenPath); + else + pathMap.put(overwrittenId.getPath(), overwrittenId); + return false; + } + } + return true; + } + + public boolean destroyRoom(ChatRoom room) { + Assert.notNull(room, "ChatRoom cannot be null!"); + synchronized (mapMutex) { + boolean success = true; + success = idMap.remove(room.getId()) != null && success; + success = pathMap.remove(room.getPath()) != null && success; + return success; + } + } + + public List getAllRooms() { + synchronized (mapMutex) { + return new ArrayList<>(idMap.values()); + } + } + + public ChatRoom getRoomById(int roomId) { + Assert.test(roomId > 0, "Room ID must be > 0!"); + synchronized (mapMutex) { + return idMap.get(roomId); + } + } + + public ChatRoom getRoomByPath(String path) { + Assert.notNull(path, "Path must not be null!"); + Assert.test(!path.isEmpty(), "Path must be non-empty!"); + Assert.test(path.startsWith("SWG."), "Path must start with \"SWG.\"!"); + synchronized (mapMutex) { + return pathMap.get(path); + } + } + + public boolean hasRoomWithId(int roomId) { + Assert.test(roomId > 0, "Room ID must be > 0!"); + synchronized (mapMutex) { + return idMap.containsKey(roomId); + } + } + + public boolean hasRoomWithPath(String path) { + Assert.notNull(path, "Path must not be null!"); + Assert.test(!path.isEmpty(), "Path must be non-empty!"); + Assert.test(path.startsWith("SWG."), "Path must start with \"SWG.\"!"); + synchronized (mapMutex) { + return pathMap.containsKey(path); + } + } + +} diff --git a/src/services/chat/ChatRoomHandler.java b/src/services/chat/ChatRoomHandler.java new file mode 100644 index 000000000..a197cd630 --- /dev/null +++ b/src/services/chat/ChatRoomHandler.java @@ -0,0 +1,366 @@ +/************************************************************************************ + * 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 java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import network.packets.swg.zone.chat.ChatOnDestroyRoom; +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.insertion.ChatRoomList; +import resources.chat.ChatAvatar; +import resources.chat.ChatResult; +import resources.chat.ChatRoom; +import resources.client_info.ServerFactory; +import resources.client_info.visitors.DatatableData; +import resources.control.Assert; +import resources.encodables.OutOfBandPackage; +import resources.player.AccessLevel; +import resources.player.Player; +import resources.server_info.CachedObjectDatabase; +import resources.server_info.Log; +import resources.server_info.ObjectDatabase; +import resources.server_info.RelationalServerData; +import resources.server_info.RelationalServerFactory; +import services.CoreManager; +import services.chat.ChatManager.ChatRange; +import services.chat.ChatManager.ChatType; + +public class ChatRoomHandler { + + private final ObjectDatabase database; + private final ChatRoomContainer rooms; + private final AtomicInteger maxChatRoomId; + private final Object roomCreationMutex; + + private RelationalServerData chatLogs; + private PreparedStatement insertChatLog; + + public ChatRoomHandler() { + this.database = new CachedObjectDatabase("odb/chat_rooms.db", ChatRoom::create, (r, s) -> r.save(s)); + this.rooms = new ChatRoomContainer(); + this.maxChatRoomId = new AtomicInteger(0); + this.roomCreationMutex = new Object(); + } + + public boolean initialize() { + database.load(); + database.traverse((room) -> { + if (room.getId() >= maxChatRoomId.get()) + maxChatRoomId.set(room.getId()); + if (room.getOwner().equals(ChatAvatar.getSystemAvatar())) + return; + rooms.addRoom(room); + }); + chatLogs = RelationalServerFactory.getServerDatabase("chat/chat_log.db"); + insertChatLog = chatLogs.prepareStatement("INSERT INTO chat_log VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + createSystemChannels(); + return true; + } + + public boolean terminate() { + database.save(); + database.close(); + chatLogs.close(); + return true; + } + + public void enterChatChannels(Player player) { + for (String channel : player.getPlayerObject().getJoinedChannels()) { + enterChatChannel(player, channel, false); + } + } + + public void enterPlanetaryChatChannels(Player player) { + // Leave old zone-only chat channels + for (String channel : new ArrayList<>(player.getPlayerObject().getJoinedChannels())) { + if (channel.endsWith(".Planet") || channel.endsWith(".system")) { + leaveChatChannel(player, channel); + } + } + + // Enter the new zone-only chat channels + String planetPath = "SWG." + player.getGalaxyName() + "." + player.getCreatureObject().getTerrain().getName() + "."; + Assert.test(rooms.hasRoomWithPath(planetPath + "Planet"), "Planet chat does not exist! planetPath = " + planetPath); + Assert.test(rooms.hasRoomWithPath(planetPath + "system"), "System chat does not exist! planetPath = " + planetPath); + enterChatChannel(player, planetPath + "Planet", false); + enterChatChannel(player, planetPath + "system", false); + } + + /** + * 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, boolean ignoreInvitation) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + ChatResult result = room.canJoinRoom(avatar, ignoreInvitation); + if (player.getAccessLevel() != AccessLevel.PLAYER) + result = ChatResult.SUCCESS; + + Log.d("enterChatChannel user=%s player=%s room=%s result=%s", player.getUsername(), player.getCharacterName(), room.getPath(), result); + if (result != ChatResult.SUCCESS) { + player.sendPacket(new ChatOnEnteredRoom(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 + player.getPlayerObject().addJoinedChannel(room.getPath()); + + // Re-send the player the room list with just this room as it could have been public/hidden + // This also "refreshes" the client, not sending this will cause a Chat channel unavailable message. + if (!room.isPublic()) + player.sendPacket(new ChatRoomList(room)); + + // Notify players of success, it's ChatResult.SUCCESS at this point + player.sendPacket(new ChatOnEnteredRoom(avatar, result, room.getId(), sequence)); + + // Notify everyone that a player entered the room + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnEnteredRoom(avatar, result, room.getId(), 0)); + + room.addMember(avatar); + } + + public void enterChatChannel(Player player, int id, int sequence) { + ChatRoom room = rooms.getRoomById(id); + if (room == null) { + player.sendPacket(new ChatOnEnteredRoom(ChatAvatar.getFromPlayer(player), ChatResult.NONE, id, sequence)); + return; + } + enterChatChannel(player, room, sequence, false); + } + + public void enterChatChannel(Player player, String path, boolean ignoreInvitation) { + ChatRoom room = rooms.getRoomByPath(path); + if (room == null) { + // Channel was not found, attempt to remove it from this players list of channels if it exists. + // This can happen if a channel was deleted while the player was offline + player.getPlayerObject().removeJoinedChannel(path); + return; + } + enterChatChannel(player, room, 0, ignoreInvitation); + } + + public void leaveChatChannel(Player player, ChatRoom room, int sequence) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + if (!room.removeMember(avatar) && !player.getPlayerObject().removeJoinedChannel(room.getPath())) + return; + + Log.d("leaveChatChannel user=%s player=%s room=%s", player.getUsername(), player.getCharacterName(), room.getPath()); + 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) { + ChatRoom room = rooms.getRoomByPath(path); + if (room == null) + return; + leaveChatChannel(player, room, 0); + } + + /** + * Creates a new chat room with the specified address path. If the path's parent channel doesn't exist, then a new chat room is created with the same passed arguments. + * + * @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 moderated Determines if the room should be moderated + * @param path Address for the channel (Ex: SWG.serverName.Imperial) + * @param title Descriptive name of the chat channel (Ex: Imperial chat for this galaxy) + * @param persist If true then this channel will be saved in an {@link ObjectDatabase} + * @return {@link ChatRoom} + */ + public void createRoom(ChatAvatar creator, boolean isPublic, boolean moderated, String path, String title, boolean persist) { + Assert.notNull(path, "Path cannot be empty!"); + Assert.test(!path.isEmpty(), "Path cannot be empty!"); + Assert.test(path.startsWith("SWG."+creator.getGalaxy()) && !path.equals("SWG."+creator.getGalaxy()), "Invalid path! " + path); + + synchronized (roomCreationMutex) { + if (rooms.getRoomByPath(path) != null) + return; + + // All paths should have parents, lets validate to make sure they exist first. Create them if they don't. + // This chunk of code makes this function recursive + int lastIndex = path.lastIndexOf('.', path.length()); + if (lastIndex != -1) { + String parentPath = path.substring(0, lastIndex); + if (!parentPath.equals("SWG."+creator.getGalaxy())) + createRoom(creator, isPublic, false, parentPath, "", persist); + } + + ChatRoom room = new ChatRoom(); + room.setId(maxChatRoomId.incrementAndGet()); + room.setOwner(creator); + room.setCreator(creator); + room.setIsPublic(isPublic); + room.setModerated(moderated); + room.setPath(path); + room.setTitle(title); + room.addModerator(creator); + rooms.addRoom(room); + + if (persist) + database.add(room); + } + } + + public void sendMessageToRoom(Player player, int roomId, int sequence, String message, OutOfBandPackage oobPackage) { + sendMessageToRoom(player, rooms.getRoomById(roomId), sequence, message, oobPackage);; + } + + public void sendMessageToRoom(Player player, String path, int sequence, String message, OutOfBandPackage oobPackage) { + sendMessageToRoom(player, rooms.getRoomByPath(path), sequence, message, oobPackage);; + } + + public void sendMessageToRoom(Player player, ChatRoom room, int sequence, String message, OutOfBandPackage oobPackage) { + if (room == null) { + player.sendPacket(new ChatOnSendRoomMessage(ChatResult.ROOM_INVALID_ID.getCode(), sequence)); + return; + } + + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + ChatResult result = room.canSendMessage(avatar); + if (result == ChatResult.SUCCESS && message.length() > 512) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + + player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), sequence)); + + if (result == ChatResult.SUCCESS) { + room.sendMessage(avatar, message, oobPackage, player.getPlayerManager()); + logChat(player.getCreatureObject().getObjectId(), player.getCharacterName(), room.getId() + "/" + room.getPath(), message); + } + } + + public boolean notifyDestroyRoom(ChatAvatar destroyer, String roomPath, int sequence) { + ChatRoom room; + + synchronized (roomCreationMutex) { + room = rooms.getRoomByPath(roomPath); + if (room == null) + return false; + rooms.destroyRoom(room); + } + + // Send the ChatOnDestroyRoom packet to every else in the room besides the person destroying the packet + ChatOnDestroyRoom packet = new ChatOnDestroyRoom(destroyer, ChatResult.SUCCESS.getCode(), room.getId(), 0); + room.getMembers().forEach(member -> { + if (!destroyer.equals(member)) + member.getPlayer().sendPacket(packet); + }); + + return true; + } + + private void createSystemChannels() { + /** + * 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 + */ + + String galaxy = CoreManager.getGalaxy().getName(); + ChatAvatar systemAvatar = ChatAvatar.getSystemAvatar(); + String basePath = "SWG." + galaxy + "."; + + DatatableData rooms = ServerFactory.getDatatable("chat/default_rooms.iff"); + rooms.handleRows((r) -> { + Log.d("Creating room: %s\t\t'%s'", basePath + rooms.getCell(r, 0), rooms.getCell(r, 1)); + createRoom(systemAvatar, true, false, basePath + rooms.getCell(r, 0), (String) rooms.getCell(r, 1), false); + }); + + createPlanetChannels(systemAvatar, basePath); + + /* + * Battlefield Room path examples: SWG.Bria.corellia.battlefield SWG.Bria.corellia.battlefield.corellia_mountain_fortress.allFactions SWG.Bria.corellia.battlefield.corellia_pvp.allFactions / Imperial / Rebel SWG.Bria.corellia.battlefield.corellia_rebel_riverside_fort.allFactions + */ + } + + private void createPlanetChannels(ChatAvatar systemAvatar, String basePath) { + DatatableData planets = ServerFactory.getDatatable("chat/planets.iff"); + planets.handleRows((r) -> { + String path = basePath + planets.getCell(r, 0) + "."; + createRoom(systemAvatar, true, false, path + "Planet", "public chat for this planet, cannot create rooms here", false); + createRoom(systemAvatar, true, false, path + "system", "system messages for this planet, cannot create rooms here", false); + createRoom(systemAvatar, true, false, path + "Chat", "public chat for this planet, can create rooms here", false); + }); + } + + public ChatRoom getRoomById(int roomId) { + return rooms.getRoomById(roomId); + } + + public ChatRoom getRoomByPath(String path) { + return rooms.getRoomByPath(path); + } + + public List getRoomList(Player player) { + ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + + List ret = new ArrayList<>(); + for (ChatRoom chatRoom : rooms.getAllRooms()) { + if (!chatRoom.isPublic() && !chatRoom.isInvited(avatar) && !chatRoom.getOwner().equals(avatar)) + continue; + ret.add(chatRoom); + } + + return ret; + } + + public void logChat(long sendId, String sendName, String room, String message) { + try { + synchronized (insertChatLog) { + insertChatLog.setLong(1, System.currentTimeMillis()); + insertChatLog.setLong(2, sendId); + insertChatLog.setString(3, sendName); + insertChatLog.setLong(4, 0); + insertChatLog.setString(5, ""); + insertChatLog.setString(6, ChatType.CHAT.name()); + insertChatLog.setString(7, ChatRange.ROOM.name()); + insertChatLog.setString(8, room); + insertChatLog.setString(9, ""); + insertChatLog.setString(10, message); + insertChatLog.executeUpdate(); + } + } catch (SQLException e) { + Log.e(e); + } + } + +} diff --git a/src/services/chat/ChatRoomService.java b/src/services/chat/ChatRoomService.java index e07ce26dc..9ae9b4b27 100644 --- a/src/services/chat/ChatRoomService.java +++ b/src/services/chat/ChatRoomService.java @@ -27,7 +27,6 @@ package services.chat; -import intents.NotifyPlayersPacketIntent; import intents.PlayerEventIntent; import intents.chat.ChatRoomUpdateIntent; import intents.network.GalacticPacketIntent; @@ -44,13 +43,10 @@ import network.packets.swg.zone.chat.ChatOnAddModeratorToRoom; import network.packets.swg.zone.chat.ChatOnBanAvatarFromRoom; import network.packets.swg.zone.chat.ChatOnCreateRoom; import network.packets.swg.zone.chat.ChatOnDestroyRoom; -import network.packets.swg.zone.chat.ChatOnEnteredRoom; import network.packets.swg.zone.chat.ChatOnInviteToRoom; import network.packets.swg.zone.chat.ChatOnKickAvatarFromRoom; -import network.packets.swg.zone.chat.ChatOnLeaveRoom; import network.packets.swg.zone.chat.ChatOnReceiveRoomInvitation; import network.packets.swg.zone.chat.ChatOnRemoveModeratorFromRoom; -import network.packets.swg.zone.chat.ChatOnSendRoomMessage; import network.packets.swg.zone.chat.ChatOnUnbanAvatarFromRoom; import network.packets.swg.zone.chat.ChatOnUninviteFromRoom; import network.packets.swg.zone.chat.ChatQueryRoom; @@ -62,53 +58,19 @@ import network.packets.swg.zone.chat.ChatSendToRoom; import network.packets.swg.zone.chat.ChatUnbanAvatarFromRoom; import network.packets.swg.zone.chat.ChatUninviteFromRoom; import network.packets.swg.zone.insertion.ChatRoomList; -import resources.Terrain; import resources.chat.ChatAvatar; import resources.chat.ChatResult; import resources.chat.ChatRoom; -import resources.client_info.ServerFactory; -import resources.client_info.visitors.DatatableData; -import resources.control.Assert; import resources.control.Service; -import resources.objects.player.PlayerObject; -import resources.player.AccessLevel; import resources.player.Player; -import resources.server_info.CachedObjectDatabase; -import resources.server_info.Log; -import resources.server_info.ObjectDatabase; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; -import resources.server_info.SynchronizedMap; -import services.CoreManager; -import services.chat.ChatManager.ChatRange; -import services.chat.ChatManager.ChatType; -import services.player.PlayerManager; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; import resources.encodables.OutOfBandPackage; -/** - * @author Waverunner - */ public class ChatRoomService extends Service { - // Map to keep track of each player's recent message for a room to prevent duplicates from client - private final ObjectDatabase database; - private final Map roomMap; - private final AtomicInteger maxChatRoomId; - - private RelationalServerData chatLogs; - private PreparedStatement insertChatLog; + private final ChatRoomHandler chatRoomHandler; public ChatRoomService() { - database = new CachedObjectDatabase("odb/chat_rooms.db", ChatRoom::create, (r, s) -> r.save(s)); - roomMap = new SynchronizedMap<>(); - maxChatRoomId = new AtomicInteger(0); + chatRoomHandler = new ChatRoomHandler(); registerForIntent(ChatRoomUpdateIntent.class, crui -> handleChatRoomUpdateIntent(crui)); registerForIntent(GalacticPacketIntent.class, gpi -> handleGalacticPacketIntent(gpi)); @@ -117,24 +79,12 @@ public class ChatRoomService extends Service { @Override public boolean initialize() { - database.load(); - database.traverse((room) -> { - if (room.getId() >= maxChatRoomId.get()) - maxChatRoomId.set(room.getId()); - roomMap.put(room.getId(), room); - }); - chatLogs = RelationalServerFactory.getServerDatabase("chat/chat_log.db"); - insertChatLog = chatLogs.prepareStatement("INSERT INTO chat_log VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - - createSystemChannels(CoreManager.getGalaxy().getName()); - return super.initialize(); + return super.initialize() && chatRoomHandler.initialize(); } @Override public boolean terminate() { - database.close(); - chatLogs.close(); - return super.terminate(); + return chatRoomHandler.terminate() && super.terminate(); } private void handleGalacticPacketIntent(GalacticPacketIntent gpi) { @@ -149,16 +99,13 @@ public class ChatRoomService extends Service { 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()); + case CHAT_ENTER_ROOM_BY_ID: + if (p instanceof ChatEnterRoomById) + chatRoomHandler.enterChatChannel(player, ((ChatEnterRoomById) p).getRoomId(), ((ChatEnterRoomById) p).getSequence()); break; - } case CHAT_REMOVE_AVATAR_FROM_ROOM: if (p instanceof ChatRemoveAvatarFromRoom) - leaveChatChannel(player, ((ChatRemoveAvatarFromRoom) p).getPath()); + chatRoomHandler.leaveChatChannel(player, ((ChatRemoveAvatarFromRoom) p).getPath()); break; case CHAT_SEND_TO_ROOM: if (p instanceof ChatSendToRoom) @@ -210,17 +157,10 @@ public class ChatRoomService extends Service { } private void handlePlayerEventIntent(PlayerEventIntent pei) { - Player player = pei.getPlayer(); - if (player == null) - return; - switch (pei.getEvent()) { - case PE_ZONE_IN_SERVER: - enterPlanetaryChatChannels(player); - break; - case PE_FIRST_ZONE: - if (player.getPlayerObject() != null) - enterChatChannels(player, player.getPlayerObject().getJoinedChannels()); + case PE_ZONE_IN_CLIENT: + chatRoomHandler.enterChatChannels(pei.getPlayer()); + chatRoomHandler.enterPlanetaryChatChannels(pei.getPlayer()); break; default: break; @@ -230,19 +170,19 @@ public class ChatRoomService extends Service { private void handleChatRoomUpdateIntent(ChatRoomUpdateIntent crui) { switch (crui.getUpdateType()) { case CREATE: - createRoom(crui.getAvatar(), crui.isPublic(), crui.getPath(), crui.getTitle()); + chatRoomHandler.createRoom(crui.getAvatar(), crui.isPublic(), false, crui.getPath(), crui.getTitle(), false); break; case DESTROY: - notifyDestroyRoom(crui.getAvatar(), crui.getPath(), 0); + chatRoomHandler.notifyDestroyRoom(crui.getAvatar(), crui.getPath(), 0); break; case JOIN: - enterChatChannel(crui.getPlayer(), crui.getPath(), crui.isIgnoreInvitation()); + chatRoomHandler.enterChatChannel(crui.getPlayer(), crui.getPath(), crui.isIgnoreInvitation()); break; case LEAVE: - leaveChatChannel(crui.getPlayer(), crui.getPath()); + chatRoomHandler.leaveChatChannel(crui.getPlayer(), crui.getPath()); break; case SEND_MESSAGE: - sendMessageToRoom(crui.getPlayer(), getRoom(crui.getPath()), 0, crui.getMessage(), new OutOfBandPackage()); + chatRoomHandler.sendMessageToRoom(crui.getPlayer(), crui.getPath(), 0, crui.getMessage(), new OutOfBandPackage()); break; default: break; @@ -252,307 +192,180 @@ public class ChatRoomService extends Service { /* Chat Rooms */ private void handleChatRemoveModeratorFromRoom(Player player, ChatRemoveModeratorFromRoom p) { - String path = p.getRoom(); ChatAvatar target = p.getAvatar(); - int sequence = p.getSequence(); - - ChatRoom room = getRoom(path); + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, ChatResult.ROOM_INVALID_NAME.getCode(), path, sequence)); - return; - } + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (!room.isModerated()) + result = ChatResult.CUSTOM_FAILURE; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!room.removeModerator(target)) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; - if (!room.isModerated()) { - player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, ChatResult.CUSTOM_FAILURE.getCode(), path, sequence)); - return; - } - - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), path, sequence)); - return; - } - - if (!room.removeModerator(target)) { - player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode(), path, sequence)); - return; - } - - player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, ChatResult.SUCCESS.getCode(), path, sequence)); + player.sendPacket(new ChatOnRemoveModeratorFromRoom(target, sender, result.getCode(), p.getRoom(), p.getSequence())); } private void handleChatAddModeratorToRoom(Player player, ChatAddModeratorToRoom p) { - String path = p.getRoom(); ChatAvatar target = p.getAvatar(); - int sequence = p.getSequence(); - - ChatRoom room = getRoom(path); + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.ROOM_INVALID_NAME.getCode(), path, sequence)); - return; - } - - if (!room.isModerated()) { - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.CUSTOM_FAILURE.getCode(), path, sequence)); - return; - } - - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), path, sequence)); - return; - } - - Player targetPlayer = player.getPlayerManager().getPlayerByCreatureFirstName(target.getName()); - if (targetPlayer == null) { - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode(), path, sequence)); - return; - } + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (!room.isModerated()) + result = ChatResult.CUSTOM_FAILURE; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!room.removeModerator(target) || player.getPlayerManager().getPlayerByCreatureFirstName(target.getName()) == null) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + else if (room.addModerator(target)) + result = ChatResult.NONE; if (room.removeBanned(target)) { // Remove player from the ban list for players that have joined the room, since this player is now a moderator - room.sendPacketToMembers(player.getPlayerManager(), new ChatOnUnbanAvatarFromRoom(path, sender, target, ChatResult.SUCCESS.getCode(), 0)); + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnUnbanAvatarFromRoom(p.getRoom(), sender, target, ChatResult.SUCCESS.getCode(), 0)); } - if (!room.addModerator(target)) { - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.NONE.getCode(), path, sequence)); - return; - } - - player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, ChatResult.SUCCESS.getCode(), path, sequence)); + player.sendPacket(new ChatOnAddModeratorToRoom(target, sender, result.getCode(), p.getRoom(), p.getSequence())); } private void handleChatUnbanAvatarFromRoom(Player player, ChatUnbanAvatarFromRoom p) { - String path = p.getRoom(); ChatAvatar target = p.getAvatar(); - int sequence = p.getSequence(); - - ChatRoom room = getRoom(path); + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnUnbanAvatarFromRoom(path, sender, target, ChatResult.ROOM_INVALID_NAME.getCode(), sequence)); - return; - } + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!room.isBanned(target) || !room.removeBanned(target)) + result = ChatResult.ROOM_AVATAR_BANNED; - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnUnbanAvatarFromRoom(path, sender, target, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), sequence)); - return; - } - - if (!room.isBanned(target) || !room.removeBanned(target)) { - player.sendPacket(new ChatOnUnbanAvatarFromRoom(path, sender, target, ChatResult.ROOM_AVATAR_BANNED.getCode(), sequence)); - return; - } - - room.sendPacketToMembers(player.getPlayerManager(), new ChatOnUnbanAvatarFromRoom(path, sender, target, ChatResult.SUCCESS.getCode(), sequence)); + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnUnbanAvatarFromRoom(p.getRoom(), sender, target, result.getCode(), p.getSequence())); } private void handleChatBanAvatarFromRoom(Player player, ChatBanAvatarFromRoom p) { - String path = p.getRoom(); ChatAvatar target = p.getAvatar(); - int sequence = p.getSequence(); - - ChatRoom room = getRoom(path); + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnBanAvatarFromRoom(path, sender, target, ChatResult.ROOM_INVALID_NAME.getCode(), sequence)); - return; - } - - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnBanAvatarFromRoom(path, sender, target, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), sequence)); - return; - } - - if (room.isBanned(target)) { - player.sendPacket(new ChatOnBanAvatarFromRoom(path, sender, target, ChatResult.ROOM_AVATAR_BANNED.getCode(), sequence)); - return; - } - - if (!room.isMember(target)) { - player.sendPacket(new ChatOnBanAvatarFromRoom(path, sender, target, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode(), sequence)); - return; - } - - if (room.isModerator(target)) - room.removeModerator(target); - - if (room.isInvited(target)) - room.removeInvited(target); + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (room.isBanned(target)) + result = ChatResult.ROOM_AVATAR_BANNED; + else if (!room.isMember(target)) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + room.removeModerator(target); + room.removeInvited(target); room.addBanned(target); - room.sendPacketToMembers(player.getPlayerManager(), new ChatOnBanAvatarFromRoom(path, sender, target, ChatResult.SUCCESS.getCode(), sequence)); + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnBanAvatarFromRoom(p.getRoom(), sender, target, result.getCode(), p.getSequence())); } private void handleChatKickAvatarFromRoom(Player player, ChatKickAvatarFromRoom p) { - String path = p.getRoom(); ChatAvatar target = p.getAvatar(); - - ChatRoom room = getRoom(path); + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnKickAvatarFromRoom(target, sender, ChatResult.ROOM_INVALID_NAME.getCode(), path)); - return; - } + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!room.isMember(target)) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + else if (player.getPlayerManager().getPlayerByCreatureFirstName(target.getName()) == null) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnKickAvatarFromRoom(target, sender, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), path)); - return; - } - - if (!room.isMember(target)) { - player.sendPacket(new ChatOnKickAvatarFromRoom(target, sender, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode(), path)); - return; - } - - Player targetPlayer = player.getPlayerManager().getPlayerByCreatureFirstName(target.getName()); - if (targetPlayer == null) { - player.sendPacket(new ChatOnKickAvatarFromRoom(target, sender, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode(), path)); - return; - } - - room.sendPacketToMembers(player.getPlayerManager(), new ChatOnKickAvatarFromRoom(target, sender, ChatResult.SUCCESS.getCode(), path)); + room.sendPacketToMembers(player.getPlayerManager(), new ChatOnKickAvatarFromRoom(target, sender, result.getCode(), p.getRoom())); } private void handleChatUninviteFromRoom(Player player, ChatUninviteFromRoom p) { - String path = p.getRoom(); - ChatRoom room = getRoom(path); - + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar invitee = p.getAvatar(); ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if (room == null) { - player.sendPacket(new ChatOnUninviteFromRoom(path, sender, invitee, ChatResult.ROOM_INVALID_NAME.getCode(), p.getSequence())); - return; - } + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (room.isPublic()) + result = ChatResult.CUSTOM_FAILURE; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!room.removeInvited(invitee)) + result = ChatResult.ROOM_PRIVATE; - if (room.isPublic()) { - player.sendPacket(new ChatOnUninviteFromRoom(path, sender, invitee, ChatResult.CUSTOM_FAILURE.getCode(), p.getSequence())); - return; - } - - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnUninviteFromRoom(path, sender, invitee, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), p.getSequence())); - return; - } - - if (!room.removeInvited(invitee)) { - player.sendPacket(new ChatOnUninviteFromRoom(path, sender, invitee, ChatResult.ROOM_PRIVATE.getCode(), p.getSequence())); - return; - } - - player.sendPacket(new ChatOnUninviteFromRoom(path, sender, invitee, ChatResult.SUCCESS.getCode(), p.getSequence())); + player.sendPacket(new ChatOnUninviteFromRoom(p.getRoom(), sender, invitee, result.getCode(), p.getSequence())); } private void handleChatInviteToRoom(Player player, ChatInviteAvatarToRoom p) { - String path = p.getRoom(); - ChatRoom room = getRoom(path); - - ChatAvatar sender = ChatAvatar.getFromPlayer(player); - if (room == null) { - player.sendPacket(new ChatOnInviteToRoom(path, sender, p.getAvatar(), ChatResult.ROOM_INVALID_NAME.getCode())); - return; - } - - if (room.isPublic()) { - player.sendPacket(new ChatOnInviteToRoom(path, sender, p.getAvatar(), ChatResult.CUSTOM_FAILURE.getCode())); - return; - } - - if (!room.isModerator(sender)) { - player.sendPacket(new ChatOnInviteToRoom(path, sender, p.getAvatar(), ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode())); - return; - } - + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoom()); ChatAvatar invitee = p.getAvatar(); + ChatAvatar sender = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; + + if (room == null) + result = ChatResult.ROOM_INVALID_NAME; + else if (room.isPublic()) + result = ChatResult.CUSTOM_FAILURE; + else if (!room.isModerator(sender)) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + Player invitedPlayer = player.getPlayerManager().getPlayerByCreatureFirstName(invitee.getName()); - if (invitedPlayer == null) { - player.sendPacket(new ChatOnInviteToRoom(path, sender, invitee, ChatResult.TARGET_AVATAR_DOESNT_EXIST.getCode())); - return; + if (result == ChatResult.SUCCESS && invitedPlayer == null) + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + + player.sendPacket(new ChatOnInviteToRoom(p.getRoom(), sender, invitee, result.getCode())); + + if (result == ChatResult.SUCCESS) { + room.addInvited(invitee); + // Notify the invited client that the room exists if not already in the clients chat lists + invitedPlayer.sendPacket(new ChatRoomList(room)); + invitedPlayer.sendPacket(new ChatOnReceiveRoomInvitation(sender, p.getRoom())); } - - player.sendPacket(new ChatOnInviteToRoom(path, sender, invitee, ChatResult.SUCCESS.getCode())); - - room.addInvited(invitee); - - // Notify the invited client that the room exists if not already in the clients chat lists - invitedPlayer.sendPacket(new ChatRoomList(room)); - - invitedPlayer.sendPacket(new ChatOnReceiveRoomInvitation(sender, path)); } private void handleChatDestroyRoom(Player player, ChatDestroyRoom p) { - ChatRoom room = roomMap.get(p.getRoomId()); + ChatRoom room = chatRoomHandler.getRoomById(p.getRoomId()); ChatAvatar avatar = ChatAvatar.getFromPlayer(player); + ChatResult result = ChatResult.SUCCESS; - if ((room == null || !room.getCreator().equals(avatar) || !room.getOwner().equals(avatar))) { - player.sendPacket(new ChatOnDestroyRoom(avatar, ChatResult.ROOM_AVATAR_NO_PERMISSION.getCode(), p.getRoomId(), p.getSequence())); - return; - } + if ((room == null || !room.getCreator().equals(avatar) || !room.getOwner().equals(avatar))) + result = ChatResult.ROOM_AVATAR_NO_PERMISSION; + else if (!chatRoomHandler.notifyDestroyRoom(avatar, room.getPath(), p.getSequence())) + result = ChatResult.NONE; - if (!notifyDestroyRoom(avatar, room.getPath(), p.getSequence())) { - player.sendPacket(new ChatOnDestroyRoom(avatar, ChatResult.NONE.getCode(), p.getRoomId(), p.getSequence())); - return; - } - - player.sendPacket(new ChatOnDestroyRoom(avatar, ChatResult.SUCCESS.getCode(), p.getRoomId(), p.getSequence())); + player.sendPacket(new ChatOnDestroyRoom(avatar, result.getCode(), p.getRoomId(), p.getSequence())); } 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) + + if (chatRoomHandler.getRoomByPath(path) != null) result = ChatResult.ROOM_ALREADY_EXISTS; + else + chatRoomHandler.createRoom(ChatAvatar.getFromPlayer(player), p.isPublic(), p.isModerated(), path, p.getRoomTitle(), true); - if (result == ChatResult.SUCCESS) { - room = createRoom(ChatAvatar.getFromPlayer(player), p.isPublic(), path, title, true); - room.setModerated(p.isModerated()); - } - - player.sendPacket(new ChatOnCreateRoom(result.getCode(), room, p.getSequence())); + player.sendPacket(new ChatOnCreateRoom(result.getCode(), chatRoomHandler.getRoomByPath(path), p.getSequence())); } private void handleChatSendToRoom(Player player, ChatSendToRoom p) { - ChatRoom room = getRoom(p.getRoomId()); - sendMessageToRoom(player, room, p.getSequence(), p.getMessage(), p.getOutOfBandPackage()); - } - - private void sendMessageToRoom(Player player, ChatRoom room, int sequence, String message, OutOfBandPackage outOfBandPackage) { - ChatResult result = ChatResult.SUCCESS; - - if (room == null) - result = ChatResult.ROOM_INVALID_ID; - - if (result != ChatResult.SUCCESS) { - player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), sequence)); - 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(), sequence)); - - if (result == ChatResult.SUCCESS) { - room.sendMessage(avatar, message, outOfBandPackage, player.getPlayerManager()); - logChat(player.getCreatureObject().getObjectId(), player.getCharacterName(), room.getId() + "/" + room.getPath(), message); - } + chatRoomHandler.sendMessageToRoom(player, p.getRoomId(), p.getSequence(), p.getMessage(), p.getOutOfBandPackage()); } private void handleChatQueryRoom(Player player, ChatQueryRoom p) { - ChatRoom room = getRoom(p.getRoomPath()); // No result code is sent for queries + ChatRoom room = chatRoomHandler.getRoomByPath(p.getRoomPath()); // No result code is sent for queries if (room == null) return; @@ -560,291 +373,7 @@ public class ChatRoomService extends Service { } private void handleChatRoomListRequest(Player player) { - ChatAvatar avatar = ChatAvatar.getFromPlayer(player); - - List rooms = new ArrayList<>(); - for (ChatRoom chatRoom : roomMap.values()) { - if (!chatRoom.isPublic() && !chatRoom.isInvited(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, false); - } - } - - 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 : new ArrayList<>(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().getTerrain(); - - // Enter the new zone-only chat channels - String planetPath = "SWG." + player.getGalaxyName() + "." + terrain.getName() + "."; - Assert.notNull(getRoom(planetPath + "Planet")); - Assert.notNull(getRoom(planetPath + "system")); - - enterChatChannel(player, planetPath + "Planet", false); - enterChatChannel(player, planetPath + "system", false); - } - - /** - * 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, boolean ignoreInvitation) { - 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, ignoreInvitation); - - 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. - if (!room.isPublic()) - player.sendPacket(new ChatRoomList(room)); - - // 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.addMember(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, false); - } - - public void enterChatChannel(Player player, String path, boolean ignoreInvitation) { - for (ChatRoom room : roomMap.values()) { - if (room.getPath().equals(path)) { - enterChatChannel(player, room, 0, ignoreInvitation); - return; - } - } - // Channel was not found, attempt to remove it from this players list of channels if it exists. - // This can happen if a channel was deleted while the player was offline - PlayerObject ghost = player.getPlayerObject(); - if (ghost == null) { - Log.e("Tried to join a room with a path that does not exist: " + path); - return; - } - ghost.removeJoinedChannel(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.removeMember(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); - break; - } - } - } - - /** - * Creates a new chat room with the specified address path. If the path's parent channel doesn't exist, then a new chat room is created with the same passed arguments. - * - * @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) - * @param persist If true then this channel will be saved in an {@link ObjectDatabase} - * @return {@link ChatRoom} - */ - public ChatRoom createRoom(ChatAvatar creator, boolean isPublic, String path, String title, boolean persist) { - if (path.isEmpty() || path.endsWith(".")) - return null; - - String base = "SWG." + creator.getGalaxy(); - if (!path.startsWith(base) || path.equals(base)) - return null; - - if (getRoom(path) != null) - return getRoom(path); - - // 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, "", persist); - } - } - - ChatRoom room = new ChatRoom(); - room.setId(maxChatRoomId.incrementAndGet()); - room.setOwner(creator); - room.setCreator(creator); - room.setIsPublic(isPublic); - room.setPath(path); - room.setTitle(title); - room.addModerator(creator); - roomMap.put(room.getId(), room); - - if (persist) - database.add(room); - return room; - } - - /** - * 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) { - return createRoom(creator, isPublic, path, title, false); - } - - public boolean notifyDestroyRoom(ChatAvatar destroyer, String roomPath, int sequence) { - ChatRoom room = getRoom(roomPath); - if (roomPath == null) - return false; - - if (!destroyRoom(room)) - return false; - - // Send the ChatOnDestroyRoom packet to every else in the room besides the person destroying the packet - List networkIds = new ArrayList<>(); - room.getMembers().forEach(member -> { - if (!destroyer.equals(member)) - networkIds.add(member.getNetworkId()); - }); - - new NotifyPlayersPacketIntent(new ChatOnDestroyRoom(destroyer, ChatResult.SUCCESS.getCode(), room.getId(), 0), networkIds).broadcast(); - - return true; - } - - public boolean destroyRoom(ChatRoom room) { - return room != null && roomMap.remove(room.getId()) != null; - } - - 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); - String basePath = "SWG." + galaxy + "."; - - DatatableData rooms = ServerFactory.getDatatable("chat/default_rooms.iff"); - rooms.handleRows((r) -> createRoom(systemAvatar, true, basePath + rooms.getCell(r, 0), (String) rooms.getCell(r, 1), true)); - - createPlanetChannels(systemAvatar, basePath); - - /* - * Battlefield Room path examples: SWG.Bria.corellia.battlefield SWG.Bria.corellia.battlefield.corellia_mountain_fortress.allFactions SWG.Bria.corellia.battlefield.corellia_pvp.allFactions / Imperial / Rebel SWG.Bria.corellia.battlefield.corellia_rebel_riverside_fort.allFactions - */ - } - - private void createPlanetChannels(ChatAvatar systemAvatar, String basePath) { - DatatableData planets = ServerFactory.getDatatable("chat/planets.iff"); - planets.handleRows((r) -> { - String path = basePath + planets.getCell(r, 0) + "."; - createRoom(systemAvatar, true, path + "Planet", "public chat for this planet, cannot create rooms here", true); - createRoom(systemAvatar, true, path + "system", "system messages for this planet, cannot create rooms here", true); - createRoom(systemAvatar, true, path + "Chat", "public chat for this planet, can create rooms here", 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); - } - - private void logChat(long sendId, String sendName, String room, String message) { - try { - synchronized (insertChatLog) { - insertChatLog.setLong(1, System.currentTimeMillis()); - insertChatLog.setLong(2, sendId); - insertChatLog.setString(3, sendName); - insertChatLog.setLong(4, 0); - insertChatLog.setString(5, ""); - insertChatLog.setString(6, ChatType.CHAT.name()); - insertChatLog.setString(7, ChatRange.ROOM.name()); - insertChatLog.setString(8, room); - insertChatLog.setString(9, ""); - insertChatLog.setString(10, message); - insertChatLog.executeUpdate(); - } - } catch (SQLException e) { - Log.e(e); - } + player.sendPacket(new ChatRoomList(chatRoomHandler.getRoomList(player))); } }