diff --git a/scripts/commands/generic/planetChat.py b/scripts/commands/generic/planetChat.py new file mode 100644 index 000000000..e25c9b15a --- /dev/null +++ b/scripts/commands/generic/planetChat.py @@ -0,0 +1,3 @@ + +def execute(galacticManager, player, target, args): + return diff --git a/src/intents/chat/ChatRoomUpdateIntent.java b/src/intents/chat/ChatRoomUpdateIntent.java new file mode 100644 index 000000000..e2093c877 --- /dev/null +++ b/src/intents/chat/ChatRoomUpdateIntent.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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 intents.chat; + +import resources.chat.ChatAvatar; +import resources.control.Intent; + +/** + * @author Waverunner + */ +public class ChatRoomUpdateIntent extends Intent { + public static final String TYPE = "ChatRoomIntent"; + + private String path; + private String title; + private String target; + private ChatAvatar avatar; + private String message; + private UpdateType updateType; + private boolean isPublic; + + public ChatRoomUpdateIntent(String path, String title, String target, ChatAvatar avatar, String message, UpdateType updateType) { + super(TYPE); + this.path = path; + this.updateType = updateType; + this.avatar = avatar; + + switch(updateType) { + case DESTROY: break; + case CREATE: this.title = title; break; + case SEND_MESSAGE: this.message = message; break; + default: this.target = target; break; + } + } + + public ChatRoomUpdateIntent(ChatAvatar avatar, String path, String title, boolean isPublic) { + this(path, title, null, avatar, null, UpdateType.CREATE); + this.isPublic = isPublic; + } + + public ChatRoomUpdateIntent(ChatAvatar avatar, String path, UpdateType updateType) { + this(path, null, null, avatar, null, updateType); + } + + public String getPath() { + return path; + } + + public String getTitle() { + return title; + } + + public String getTarget() { + return target; + } + + public ChatAvatar getAvatar() { + return avatar; + } + + public String getMessage() { + return message; + } + + public UpdateType getUpdateType() { + return updateType; + } + + public boolean isPublic() { + return isPublic; + } + + public enum UpdateType { + CREATE, + DESTROY, + JOIN, + LEAVE, + MODERATORS_ADD_TARGET, + MODERATORS_REMOVE_TARGET, + BANNED_ADD_TARGET, + BANNED_REMOVE_TARGET, + INVITED_ADD_TARGET, + INVITED_REMOVE_TARGET, + SEND_MESSAGE + } +} diff --git a/src/network/PacketType.java b/src/network/PacketType.java index e4645342e..de5141510 100644 --- a/src/network/PacketType.java +++ b/src/network/PacketType.java @@ -121,9 +121,12 @@ public enum PacketType { CHAT_PERSISTENT_MESSAGE_TO_SERVER (0x25A29FA6, ChatPersistentMessageToServer.class), CHAT_DELETE_PERSISTENT_MESSAGE (0x8F251641, ChatDeletePersistentMessage.class), CHAT_REQUEST_PERSISTENT_MESSAGE (0x07E3559F, ChatRequestPersistentMessage.class), - CHAT_REQUEST_ROOM_LIST (0x4C3d2CfA, ChatRequestRoomList.class), - CHAT_ROOM_LIST (0x70DEB197, ChatRoomList.class), - CHAT_ROOM_MESSAGE (0xCD4CE444, ChatRoomMessage.class), + CHAT_REQUEST_ROOM_LIST (ChatRequestRoomList.CRC, ChatRequestRoomList.class), + CHAT_ENTER_ROOM_BY_ID (ChatEnterRoomById.CRC, ChatEnterRoomById.class), + CHAT_QUERY_ROOM (ChatQueryRoom.CRC, ChatQueryRoom.class), + CHAT_ROOM_LIST (ChatRoomList.CRC, ChatRoomList.class), + CHAT_ROOM_MESSAGE (ChatRoomMessage.CRC, ChatRoomMessage.class), + CHAT_SEND_TO_ROOM (ChatSendToRoom.CRC, ChatSendToRoom.class), CHAT_SERVER_STATUS (0x7102B15F, ChatServerStatus.class), CHAT_SYSTEM_MESSAGE (0x6D2A6413, ChatSystemMessage.class), CON_GENERIC_MESSAGE (0x08C5FC76, ConGenericMessage.class), diff --git a/src/network/packets/Packet.java b/src/network/packets/Packet.java index af0742e69..b1cf7c78f 100644 --- a/src/network/packets/Packet.java +++ b/src/network/packets/Packet.java @@ -27,10 +27,13 @@ ***********************************************************************************/ package network.packets; +import resources.network.BaselineBuilder; + import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; +import java.util.List; public class Packet { @@ -72,7 +75,14 @@ public class Packet { public int getOpcode() { return opcode; } - + + public static void addList(ByteBuffer bb, List list) { + addInt(bb, list.size()); + for (BaselineBuilder.Encodable encodable : list) { + addData(bb, encodable.encode()); + } + } + public static void addBoolean(ByteBuffer bb, boolean b) { bb.put(b ? (byte)1 : (byte)0); } @@ -120,7 +130,11 @@ public class Packet { public static void addByte(ByteBuffer bb, int b) { bb.put((byte)b); } - + + public static void addData(ByteBuffer bb, byte[] data) { + bb.put(data); + } + public static void addArray(ByteBuffer bb, byte [] b) { addShort(bb, b.length); bb.put(b); diff --git a/src/network/packets/swg/SWGPacket.java b/src/network/packets/swg/SWGPacket.java index 470b7d645..962e122e4 100644 --- a/src/network/packets/swg/SWGPacket.java +++ b/src/network/packets/swg/SWGPacket.java @@ -32,6 +32,7 @@ import java.nio.ByteBuffer; import resources.Location; import network.PacketType; import network.packets.Packet; +import resources.encodables.OutOfBandPackage; public class SWGPacket extends Packet { @@ -61,7 +62,7 @@ public class SWGPacket extends Packet { l.setZ(getFloat(data)); return l; } - + public void setSWGOpcode(int opcode) { this.opcode = opcode; this.type = PacketType.fromCrc(opcode); @@ -101,4 +102,10 @@ public class SWGPacket extends Packet { public ByteBuffer getData() { return data; } + + public static OutOfBandPackage getOutOfBand(ByteBuffer data) { + OutOfBandPackage outOfBandPackage = new OutOfBandPackage(); + outOfBandPackage.decode(data); + return outOfBandPackage; + } } diff --git a/src/network/packets/swg/zone/chat/ChatEnterRoomById.java b/src/network/packets/swg/zone/chat/ChatEnterRoomById.java new file mode 100644 index 000000000..54e6f253c --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatEnterRoomById.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatEnterRoomById extends SWGPacket { + public static final int CRC = resources.common.CRC.getCrc("ChatEnterRoomById"); + + private int sequence; + private int roomId; + private String roomPath; + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + + sequence = getInt(data); + roomId = getInt(data); + roomPath = getAscii(data); + System.out.println("ChatEnterRoomById: " + roomId + roomPath); + } + + @Override + public ByteBuffer encode() { + ByteBuffer bb = ByteBuffer.allocate(10 + roomPath.length()); + addShort(bb, 4); + addInt(bb, sequence); + addInt(bb, roomId); + addAscii(bb, roomPath); + return bb; + } + + public int getSequence() { + return sequence; + } + + public int getRoomId() { + return roomId; + } + + public String getRoomPath() { + return roomPath; + } +} diff --git a/src/network/packets/swg/zone/chat/ChatOnConnectAvatar.java b/src/network/packets/swg/zone/chat/ChatOnConnectAvatar.java index 99fc393c5..6a542e2f0 100644 --- a/src/network/packets/swg/zone/chat/ChatOnConnectAvatar.java +++ b/src/network/packets/swg/zone/chat/ChatOnConnectAvatar.java @@ -48,8 +48,7 @@ public class ChatOnConnectAvatar extends SWGPacket { } public void decode(ByteBuffer data) { - if (!super.decode(data, CRC)) - return; + super.decode(data, CRC); } public ByteBuffer encode() { diff --git a/src/network/packets/swg/zone/chat/ChatOnDestroyRoom.java b/src/network/packets/swg/zone/chat/ChatOnDestroyRoom.java index 1f735def6..4b99fc002 100644 --- a/src/network/packets/swg/zone/chat/ChatOnDestroyRoom.java +++ b/src/network/packets/swg/zone/chat/ChatOnDestroyRoom.java @@ -30,27 +30,22 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; import network.packets.swg.SWGPacket; +import resources.chat.ChatAvatar; public class ChatOnDestroyRoom extends SWGPacket { public static final int CRC = 0xE8EC5877; - - private String galaxy; - private String owner; - private boolean success; + + private ChatAvatar owner; + private int result; private int roomId; - private int requestId; - - public ChatOnDestroyRoom() { - this("", "", true, 0, 0); - } - - public ChatOnDestroyRoom(String galaxy, String owner, boolean success, int roomId, int requestId) { - this.galaxy = galaxy; + private int sequence; + + public ChatOnDestroyRoom(ChatAvatar owner, int result, int roomId, int sequence) { this.owner = owner; - this.success = success; + this.result = result; this.roomId = roomId; - this.requestId = requestId; + this.sequence = sequence; } public ChatOnDestroyRoom(ByteBuffer data) { @@ -60,24 +55,21 @@ public class ChatOnDestroyRoom extends SWGPacket { public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - getAscii(data); // SWG - galaxy = getAscii(data); - owner = getAscii(data); - success = getInt(data) == 0; + owner = new ChatAvatar(); + owner.decode(data); + result = getInt(data); roomId = getInt(data); - requestId = getInt(data); + sequence = getInt(data); } public ByteBuffer encode() { - ByteBuffer data = ByteBuffer.allocate(27 + galaxy.length() + owner.length()); + ByteBuffer data = ByteBuffer.allocate(18 + owner.encode().length); addShort(data, 5); addInt (data, CRC); - addAscii(data, "SWG"); - addAscii(data, galaxy); - addAscii(data, owner); - addInt (data, success ? 0 : 1); + data.put(owner.encode()); + addInt (data, result); addInt (data, roomId); - addInt (data, requestId); + addInt (data, sequence); return data; } diff --git a/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java b/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java index ad3a43120..851902eba 100644 --- a/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java +++ b/src/network/packets/swg/zone/chat/ChatOnEnteredRoom.java @@ -30,76 +30,52 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; import network.packets.swg.SWGPacket; +import resources.chat.ChatAvatar; +import resources.chat.ChatResult; public class ChatOnEnteredRoom extends SWGPacket { public static final int CRC = 0xE69BDC0A; - - private String galaxy; - private String character; - private ChannelStatus status; + + private ChatAvatar avatar; + private int result; private int chatRoomId; - - public ChatOnEnteredRoom() { - this("", "", ChannelStatus.SUCCESS, 0); - } - - public ChatOnEnteredRoom(String galaxy, String character, ChannelStatus status, int chatRoomId) { - this.galaxy = galaxy; - this.character = character; - this.status = status; + private int sequence; + + public ChatOnEnteredRoom(ChatAvatar avatar, int chatRoomId, int sequence) { + this.avatar = avatar; this.chatRoomId = chatRoomId; + this.sequence = sequence; } public ChatOnEnteredRoom(ByteBuffer data) { decode(data); } - + public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - getAscii(data); - galaxy = getAscii(data); - character = getAscii(data); - status = ChannelStatus.fromInteger(getInt(data)); + avatar = new ChatAvatar(); + avatar.decode(data); + result = getInt(data); chatRoomId = getInt(data); - getInt(data); + sequence = getInt(data); } public ByteBuffer encode() { - ByteBuffer data = ByteBuffer.allocate(27 + galaxy.length() + character.length()); + byte[] avatarData = avatar.encode(); + ByteBuffer data = ByteBuffer.allocate(avatarData.length + 18); addShort(data, 5); addInt (data, CRC); - addAscii(data, "SWG"); - addAscii(data, galaxy); - addAscii(data, character); - addInt (data, status.getBitmask()); + data.put(avatarData); + addInt(data, result); addInt (data, chatRoomId); - addInt (data, 0); + addInt (data, sequence); return data; } - - public enum ChannelStatus { - SUCCESS(0), // "You have joined the channel." - NOT_INVITED(0x10), // "You cannot join '%TU (room name)' because you are not invted to the room." - UNKNOWN(0x20); // "Chatroom '%TU (room name)' join failed for an unknown reason." - - private int bitmask; - - ChannelStatus(int bitmask) { - this.bitmask = bitmask; - } - public int getBitmask() { - return bitmask; - } - public static final ChannelStatus fromInteger(int i) { - if (i == 0) - return SUCCESS; - if (i == 0x10) - return NOT_INVITED; - return UNKNOWN; - } - }; - + + public void setResult(int result) { + this.result = result; + } } diff --git a/src/network/packets/swg/zone/chat/ChatOnLeaveRoom.java b/src/network/packets/swg/zone/chat/ChatOnLeaveRoom.java index c971b4629..ee1dd7285 100644 --- a/src/network/packets/swg/zone/chat/ChatOnLeaveRoom.java +++ b/src/network/packets/swg/zone/chat/ChatOnLeaveRoom.java @@ -30,27 +30,22 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; import network.packets.swg.SWGPacket; +import resources.chat.ChatAvatar; public class ChatOnLeaveRoom extends SWGPacket { public static final int CRC = 0x60B5098B; - private String galaxyName; - private String characterName; - private int errorCode; + private ChatAvatar avatar; + private int result; private int chatRoomId; - private int requestId; - - public ChatOnLeaveRoom() { - this("", "", 0, 0, 0); - } - - public ChatOnLeaveRoom(String galaxy, String character, int errorCode, int chatRoomId, int requestId) { - this.galaxyName = galaxy; - this.characterName = character; - this.errorCode = errorCode; + private int sequence; + + public ChatOnLeaveRoom(ChatAvatar avatar, int errorCode, int chatRoomId, int sequence) { + this.avatar = avatar; + this.result = errorCode; this.chatRoomId = chatRoomId; - this.requestId = requestId; + this.sequence = sequence; } public ChatOnLeaveRoom(ByteBuffer data) { @@ -60,24 +55,21 @@ public class ChatOnLeaveRoom extends SWGPacket { public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - getAscii(data); - galaxyName = getAscii(data); - characterName = getAscii(data); - errorCode = getInt(data); + avatar = new ChatAvatar(); + avatar.decode(data); + result = getInt(data); chatRoomId = getInt(data); - requestId = getInt(data); + sequence = getInt(data); } public ByteBuffer encode() { - ByteBuffer data = ByteBuffer.allocate(27+galaxyName.length()+characterName.length()); + ByteBuffer data = ByteBuffer.allocate(18 + avatar.encode().length); addShort(data, 2); addInt( data, CRC); - addAscii(data, "SWG"); - addAscii(data, galaxyName); - addAscii(data, characterName); - addInt (data, errorCode); + data.put(avatar.encode()); + addInt(data, result); addInt (data, chatRoomId); - addInt (data, requestId); + addInt (data, sequence); return data; } diff --git a/src/network/packets/swg/zone/chat/ChatOnReceiveRoomInvitation.java b/src/network/packets/swg/zone/chat/ChatOnReceiveRoomInvitation.java index 7ee7ef5d1..e0d1fe26d 100644 --- a/src/network/packets/swg/zone/chat/ChatOnReceiveRoomInvitation.java +++ b/src/network/packets/swg/zone/chat/ChatOnReceiveRoomInvitation.java @@ -35,21 +35,14 @@ public class ChatOnReceiveRoomInvitation extends SWGPacket { public static final int CRC = 0xC17EB06D; - public ChatOnReceiveRoomInvitation() { - - } - - public ChatOnReceiveRoomInvitation(String command) { - - } - + public ChatOnReceiveRoomInvitation() {} + public ChatOnReceiveRoomInvitation(ByteBuffer data) { decode(data); } public void decode(ByteBuffer data) { - if (!super.decode(data, CRC)) - return; + super.decode(data, CRC); } public ByteBuffer encode() { diff --git a/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java b/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java index 55863ba5c..daa903035 100644 --- a/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java +++ b/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java @@ -28,10 +28,9 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import network.packets.swg.SWGPacket; -import services.chat.ChatResult; +import resources.chat.ChatResult; public class ChatOnSendPersistentMessage extends SWGPacket { public static final int CRC = 0x94E7A7AE; diff --git a/src/network/packets/swg/zone/chat/ChatOnSendRoomMessage.java b/src/network/packets/swg/zone/chat/ChatOnSendRoomMessage.java index b293c1503..419bf340b 100644 --- a/src/network/packets/swg/zone/chat/ChatOnSendRoomMessage.java +++ b/src/network/packets/swg/zone/chat/ChatOnSendRoomMessage.java @@ -32,34 +32,29 @@ import java.nio.ByteBuffer; import network.packets.swg.SWGPacket; public class ChatOnSendRoomMessage extends SWGPacket { - - public static final int CRC = 0xE7B61633; - private int error = 0; - private int messageId = 0; - - public ChatOnSendRoomMessage() { - - } - - public ChatOnSendRoomMessage(int error, int messageId) { - this.error = error; - this.messageId = messageId; + public static final int CRC = resources.common.CRC.getCrc("ChatOnSendRoomMessage"); + + private int result = 0; + private int sequence = 0; + + public ChatOnSendRoomMessage(int result, int sequence) { + this.result = result; + this.sequence = sequence; } public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - error = getInt(data); - messageId = getInt(data); + result = getInt(data); + sequence = getInt(data); } public ByteBuffer encode() { - int length = 14; - ByteBuffer data = ByteBuffer.allocate(length); + ByteBuffer data = ByteBuffer.allocate(14); addShort(data, 3); addInt( data, CRC); - addInt( data, error); - addInt( data, messageId); + addInt( data, result); + addInt( data, sequence); return data; } } diff --git a/src/network/packets/swg/zone/chat/ChatQueryRoom.java b/src/network/packets/swg/zone/chat/ChatQueryRoom.java new file mode 100644 index 000000000..8fa63c24c --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatQueryRoom.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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 network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatQueryRoom extends SWGPacket { + public static final int CRC = resources.common.CRC.getCrc("ChatQueryRoom"); + + private int sequence; + private String roomPath; + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + sequence = getInt(data); + roomPath = getAscii(data); + } + + @Override + public ByteBuffer encode() { + ByteBuffer bb = ByteBuffer.allocate(12 + roomPath.length()); + addShort(bb, 3); + addInt(bb, CRC); + addInt(bb, sequence); + addAscii(bb, roomPath); + return bb; + } + + public int getSequence() { + return sequence; + } + + public String getRoomPath() { + return roomPath; + } +} diff --git a/src/network/packets/swg/zone/chat/ChatQueryRoomResults.java b/src/network/packets/swg/zone/chat/ChatQueryRoomResults.java new file mode 100644 index 000000000..5fe4bb202 --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatQueryRoomResults.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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 network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; +import resources.chat.ChatAvatar; +import resources.chat.ChatRoom; +import resources.network.BaselineBuilder; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * @author Waverunner + */ +public class ChatQueryRoomResults extends SWGPacket { + public static final int CRC = resources.common.CRC.getCrc("ChatQueryRoomResults"); + + private ChatRoom room; + private int sequence; + + public ChatQueryRoomResults(ChatRoom room, int sequence) { + this.room = room; + this.sequence = sequence; + } + + @Override + public void decode(ByteBuffer data) { + super.decode(data, CRC); + } + + @Override + public ByteBuffer encode() { + byte[] data = room.encode(); + int size = data.length; + + List members = room.getMembers(); + List invited = room.getInvited(); + List moderators = room.getModerators(); + List banned = room.getBanned(); + + for (ChatAvatar avatar : members) { size += avatar.getSize(); } + for (ChatAvatar avatar : invited) { size += avatar.getSize(); } + for (ChatAvatar avatar : moderators) { size += avatar.getSize(); } + for (ChatAvatar avatar : banned) { size += avatar.getSize(); } + + ByteBuffer bb = ByteBuffer.allocate(20 + size); + addList(bb, members); + addList(bb, invited); + addList(bb, moderators); + addList(bb, banned); + addInt(bb, sequence); + addData(bb, data); + + return bb; + } +} diff --git a/src/network/packets/swg/zone/ChatRequestRoomList.java b/src/network/packets/swg/zone/chat/ChatRequestRoomList.java similarity index 95% rename from src/network/packets/swg/zone/ChatRequestRoomList.java rename to src/network/packets/swg/zone/chat/ChatRequestRoomList.java index 596192497..051c232fe 100644 --- a/src/network/packets/swg/zone/ChatRequestRoomList.java +++ b/src/network/packets/swg/zone/chat/ChatRequestRoomList.java @@ -25,7 +25,7 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -package network.packets.swg.zone; +package network.packets.swg.zone.chat; import java.nio.ByteBuffer; @@ -35,13 +35,10 @@ public class ChatRequestRoomList extends SWGPacket { public static final int CRC = 0x4C3D2CFA; - public ChatRequestRoomList() { - - } + public ChatRequestRoomList() {} public void decode(ByteBuffer data) { - if (!super.decode(data, CRC)) - return; + super.decode(data, CRC); } public ByteBuffer encode() { diff --git a/src/network/packets/swg/zone/chat/ChatRoomMessage.java b/src/network/packets/swg/zone/chat/ChatRoomMessage.java index f81092c06..2bf864fd1 100644 --- a/src/network/packets/swg/zone/chat/ChatRoomMessage.java +++ b/src/network/packets/swg/zone/chat/ChatRoomMessage.java @@ -30,49 +30,43 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; import network.packets.swg.SWGPacket; -import network.packets.swg.zone.insertion.ChatRoomList.ChatRoom.User; +import resources.chat.ChatAvatar; +import resources.encodables.OutOfBandPackage; public class ChatRoomMessage extends SWGPacket { - - public static final int CRC = 0xCD4CE444; - private User user = new User(); + public static final int CRC = resources.common.CRC.getCrc("ChatRoomMessage"); + + private ChatAvatar avatar; private int roomId = 0; private String message = ""; - - public ChatRoomMessage() { - - } - - public ChatRoomMessage(String game, String server, String name, int roomId, String message) { - this.user.game = game; - this.user.server = server; - this.user.name = name; + private OutOfBandPackage outOfBandPackage; + + public ChatRoomMessage(ChatAvatar avatar, int roomId, String message, OutOfBandPackage oob) { + this.avatar = avatar; this.roomId = roomId; this.message = message; + this.outOfBandPackage = oob; } public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; - user.game = getAscii(data); - user.server = getAscii(data); - user.name = getAscii(data); + avatar.decode(data); roomId = getInt(data); message = getUnicode(data); - getUnicode(data); + outOfBandPackage.decode(data); } public ByteBuffer encode() { - int length = 24 + user.game.length() + user.server.length() + user.name.length() + message.length() * 2; + byte[] oob = outOfBandPackage.encode(); + int length = 14 + avatar.encode().length + oob.length + message.length() * 2; ByteBuffer data = ByteBuffer.allocate(length); addShort( data, 5); addInt( data, CRC); - addAscii( data, user.game); - addAscii( data, user.server); - addAscii( data, user.name); + data.put(avatar.encode()); addInt( data, roomId); addUnicode(data, message); - addUnicode(data, ""); + data.put(outOfBandPackage.encode()); return data; } } diff --git a/src/network/packets/swg/zone/chat/ChatSendToRoom.java b/src/network/packets/swg/zone/chat/ChatSendToRoom.java new file mode 100644 index 000000000..ed691d500 --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatSendToRoom.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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 network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; +import resources.encodables.OutOfBandPackage; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatSendToRoom extends SWGPacket { + public static final int CRC = resources.common.CRC.getCrc("ChatSendToRoom"); + + private String message; + private OutOfBandPackage outOfBandPackage; + private int roomId; + private int sequence; // This seems to vary from room to room, client keeps track individually for each room? + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + + message = getUnicode(data); + outOfBandPackage = getOutOfBand(data); + roomId = getInt(data); + sequence = getInt(data); + } + + @Override + public ByteBuffer encode() { + return super.encode(); + } + + public String getMessage() { + return message; + } + + public OutOfBandPackage getOutOfBandPackage() { + return outOfBandPackage; + } + + public int getRoomId() { + return roomId; + } + + public int getSequence() { + return sequence; + } +} diff --git a/src/network/packets/swg/zone/insertion/ChatRoomList.java b/src/network/packets/swg/zone/insertion/ChatRoomList.java index facb3ac74..2aa2caa4e 100644 --- a/src/network/packets/swg/zone/insertion/ChatRoomList.java +++ b/src/network/packets/swg/zone/insertion/ChatRoomList.java @@ -28,209 +28,47 @@ package network.packets.swg.zone.insertion; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Vector; +import java.util.Arrays; +import java.util.Collection; import network.packets.swg.SWGPacket; -import network.packets.swg.zone.insertion.ChatRoomList.ChatRoom.User; +import resources.chat.ChatRoom; public class ChatRoomList extends SWGPacket { - - public static final int CRC = 0x70DEB197; - private List chatrooms = new ArrayList(); - - public ChatRoomList() { - + public static final int CRC = resources.common.CRC.getCrc("ChatRoomList"); + + private Collection rooms; + + public ChatRoomList(ChatRoom ... rooms) { + this.rooms = Arrays.asList(rooms); } - - public ChatRoomList(ChatRoom room) { - chatrooms.add(room); + + public ChatRoomList(Collection rooms) { + this.rooms = rooms; } - - public ChatRoomList(ChatRoom [] rooms) { - for (ChatRoom r : rooms) { - chatrooms.add(r); - } - } - - public void addChatRoom(ChatRoom r) { - chatrooms.add(r); - } - + public void decode(ByteBuffer data) { if (!super.decode(data, CRC)) return; int chatroomCount = getInt(data); for (int i = 0; i < chatroomCount; i++) { ChatRoom room = new ChatRoom(); - room.setRoomId(getInt(data)); - room.setPrivateFlag(getInt(data)); - room.setModeratedFlag(getByte(data)); - room.setRoomPathName(getAscii(data)); - room.setGame(getAscii(data)); - room.setServer(getAscii(data)); - room.setOwner(getAscii(data)); - getAscii(data); // SWG - getAscii(data); // galaxy - room.setCreator(getAscii(data)); - room.setTitle(getUnicode(data)); - int moderatorCount = getInt(data); - for (int m = 0; m < moderatorCount; m++) { - User user = new User(); - user.setGame(getAscii(data)); - user.setServer(getAscii(data)); - user.setName(getAscii(data)); - room.addModerator(user); - } - int userCount = getInt(data); - for (int u = 0; u < userCount; u++) { - User user = new User(); - user.setGame(getAscii(data)); - user.setServer(getAscii(data)); - user.setName(getAscii(data)); - room.addUser(user); - } + room.decode(data); } } public ByteBuffer encode() { int length = 10; - for (ChatRoom r : chatrooms) { - length += r.getSize(); + for (ChatRoom r : rooms) { + length += r.encode().length; } ByteBuffer data = ByteBuffer.allocate(length); addShort(data, 2); addInt( data, CRC); - addInt( data, chatrooms.size()); - for (ChatRoom r : chatrooms) { - addInt(data, r.getRoomId()); - addInt(data, r.getPrivateFlag()); - addByte(data, r.getModeratedFlag()); - addAscii(data, r.getRoomPathName()); - addAscii(data, r.getGame()); - addAscii(data, r.getServer()); - addAscii(data, r.getOwner()); - addAscii(data, r.getCreator()); - addUnicode(data, r.getTitle()); - addInt(data, r.getModerators().size()); - for (User u : r.getModerators()) { - addAscii(data, u.game); - addAscii(data, u.server); - addAscii(data, u.name); - } - addInt(data, r.getUsers().size()); - for (User u : r.getUsers()) { - addAscii(data, u.game); - addAscii(data, u.server); - addAscii(data, u.name); - } + addInt(data, rooms.size()); + for (ChatRoom room : rooms) { + data.put(room.encode()); } return data; } - - public static class ChatRoom { - private int roomId = 0; - private int privateFlag = 0; - private int moderatedFlag = 0; - private String roomPathName = ""; - private String game = ""; - private String server = ""; - private String owner = ""; - private String creator = ""; - private String title = ""; - private Vector moderators = new Vector(); - private Vector users = new Vector(); - - public ChatRoom() { - - } - - public ChatRoom(int roomId, int privateFlag, int moderatedFlag, String roomPathName, String game, String server, String owner, String creator, String title) { - this.roomId = roomId; - this.privateFlag = privateFlag; - this.moderatedFlag = moderatedFlag; - this.roomPathName = roomPathName; - this.game = game; - this.server = server; - this.owner = owner; - this.creator = creator; - this.title = title; - } - - public void addModerator(String game, String server, String name) { - moderators.add(new User(game, server, name)); - } - - public void addUser(String game, String server, String name) { - users.add(new User(game, server, name)); - } - - public int getSize() { - int length = 31; - length += roomPathName.length(); - length += game.length(); - length += server.length(); - length += owner.length(); - length += creator.length(); - length += title.length() * 2; - for (User u : moderators) length += u.getSize(); - for (User u : users) length += u.getSize(); - return length; - } - - public int getRoomId() { return roomId; } - public int getPrivateFlag() { return privateFlag; } - public int getModeratedFlag() { return moderatedFlag; } - public String getRoomPathName() { return roomPathName; } - public String getGame() { return game; } - public String getServer() { return server; } - public String getOwner() { return owner; } - public String getCreator() { return creator; } - public String getTitle() { return title; } - public List getUsers() { return users; } - public List getModerators() { return moderators; } - - public void setRoomId(int id) { this.roomId = id; } - public void setPrivateFlag(int flag) { this.privateFlag = flag; } - public void setModeratedFlag(int flag) { this.moderatedFlag = flag; } - public void setRoomPathName(String path) { this.roomPathName = path; } - public void setGame(String game) { this.game = game; } - public void setServer(String server) { this.server = server; } - public void setOwner(String owner) { this.owner = owner; } - public void setCreator(String creator) { this.creator = creator; } - public void setTitle(String title) { this.title = title; } - public void setUsers(Vector users) { this.users = users; } - public void setModerators(Vector moderators) { this.moderators = moderators; } - public void addUser(User user) { this.users.add(user); } - public void addModerator(User moderator) { this.moderators.add(moderator); } - - public static class User { - public String game = ""; - public String server = ""; - public String name = ""; - - public User() { - - } - - public User(String game, String server, String name) { - this.game = game; - this.server = server; - this.name = name; - } - - public int getSize() { - return 6 + game.length() + server.length() + name.length(); - } - - public String getGame() { return game; } - public String getServer() { return server; } - public String getName() { return name; } - - public void setGame(String game) { this.game = game; } - public void setServer(String server) { this.server = server; } - public void setName(String name) { this.name = name; } - } - } } diff --git a/src/resources/chat/ChatAvatar.java b/src/resources/chat/ChatAvatar.java new file mode 100644 index 000000000..bb7a7002c --- /dev/null +++ b/src/resources/chat/ChatAvatar.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * 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.chat; + +import network.packets.Packet; +import resources.network.BaselineBuilder; +import resources.objects.creature.CreatureObject; +import resources.player.Player; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatAvatar implements BaselineBuilder.Encodable, BaselineBuilder.Decodable { + private long networkId; + 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; + } + + public ChatAvatar(long networkId, String name, String galaxy) { + this(); + this.networkId = networkId; + this.name = name; + this.galaxy = galaxy; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + modified = true; + } + + public String getGame() { + return game; + } + + public void setGame(String game) { + this.game = game; + modified = true; + } + + public String getGalaxy() { + return galaxy; + } + + public void setGalaxy(String galaxy) { + this.galaxy = galaxy; + modified = true; + } + + public long getNetworkId() { + return networkId; + } + + public int getSize() { + if (size == 0) + size = 6 + game.length() + name.length() + galaxy.length(); + + return size; + } + + @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; + } + + @Override + public void decode(ByteBuffer data) { + + } + + @Override + public String toString() { + return "ChatAvatar:" + getNetworkId() + "[" + getGame() + "." + getGalaxy() + "." + getName() + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + + if (getClass() != o.getClass()) + return false; + + return ((ChatAvatar) o).getNetworkId() == networkId; + } + + @Override + public int hashCode() { + return (int) (networkId ^ (networkId >>> 32)); + } + + public static ChatAvatar getFromPlayer(Player player) { + return new ChatAvatar(player.getNetworkId(), player.getCharacterName().split(" ")[0].toLowerCase(), player.getGalaxyName()); + } + + public static ChatAvatar getSystemAvatar(String galaxy) { + return new ChatAvatar(-1, "system", galaxy); + } +} diff --git a/src/services/chat/ChatResult.java b/src/resources/chat/ChatResult.java similarity index 74% rename from src/services/chat/ChatResult.java rename to src/resources/chat/ChatResult.java index 700d294d9..65f4cd8d3 100644 --- a/src/services/chat/ChatResult.java +++ b/src/resources/chat/ChatResult.java @@ -25,16 +25,25 @@ * along with Holocore. If not, see ******************************************************************************/ -package services.chat; +package resources.chat; /** * @author Waverunner */ public enum ChatResult { - NONE(-1), + NONE(-1), // The client will just display an "unknown error code" if this is used. SUCCESS(0), TARGET_AVATAR_DOESNT_EXIST(4), - IGNORED(23); + ROOM_INVALID_ID(5), + ROOM_INVALID_NAME(6), + ROOM_NOT_MODERATOR(9), + ROOM_AVATAR_BANNED(12), + ROOM_AVATAR_NOT_INVITED(16), + IGNORED(23), + ROOM_ALREADY_JOINED(36), + CHAT_SERVER_UNAVAILABLE(1000000), + ROOM_DIFFERENT_FACTION(1000001), + ROOM_NOT_GCW_DEFENDER_FACTION(1000005); private final int code; @@ -45,4 +54,12 @@ public enum ChatResult { public int getCode() { return code; } + + public static ChatResult fromInteger(int code) { + for (ChatResult result : ChatResult.values()) { + if (code == result.getCode()) + return result; + } + return NONE; + } } diff --git a/src/resources/chat/ChatRoom.java b/src/resources/chat/ChatRoom.java new file mode 100644 index 000000000..8a4ccf92a --- /dev/null +++ b/src/resources/chat/ChatRoom.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * 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.chat; + +import network.packets.Packet; +import network.packets.swg.zone.chat.ChatRoomMessage; +import resources.encodables.OutOfBandPackage; +import resources.network.BaselineBuilder; +import resources.player.Player; +import services.player.PlayerManager; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Waverunner + */ +public class ChatRoom implements Serializable, BaselineBuilder.Encodable, BaselineBuilder.Decodable { + private static final long serialVersionUID = 1L; + + private int id; + private int type; + private String path; + private ChatAvatar owner; + private ChatAvatar creator; + private String title; + private List moderators; + private List invited; + private boolean muted; // No one but moderators can talk + private boolean isPublic; + private List banned; + // 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; + + public ChatRoom() { + moderators = new ArrayList<>(); + invited = new ArrayList<>(); + members = new ArrayList<>(); + banned = new ArrayList<>(); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public ChatAvatar getOwner() { + return owner; + } + + public void setOwner(ChatAvatar owner) { + this.owner = owner; + } + + public ChatAvatar getCreator() { + return creator; + } + + public void setCreator(ChatAvatar creator) { + this.creator = creator; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List getModerators() { + return moderators; + } + + public List getInvited() { + return invited; + } + + public boolean isMuted() { + return muted; + } + + public void setMuted(boolean muted) { + this.muted = muted; + } + + public List getMembers() { + return members; + } + + public boolean isPublic() { + return isPublic; + } + + public void setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + public List getBanned() { + return banned; + } + + public ChatResult canJoinRoom(ChatAvatar avatar) { + if (banned.contains(avatar)) + return ChatResult.ROOM_AVATAR_BANNED; + + if (members.contains(avatar)) + return ChatResult.ROOM_ALREADY_JOINED; + + if (isPublic || invited.contains(avatar)) + return ChatResult.SUCCESS; + + return ChatResult.ROOM_AVATAR_NOT_INVITED; + } + + public ChatResult canSendMessage(ChatAvatar avatar) { + if (banned.contains(avatar)) + return ChatResult.ROOM_AVATAR_BANNED; + + if (muted && !moderators.contains(avatar)) + return ChatResult.ROOM_NOT_MODERATOR; + + return ChatResult.SUCCESS; + } + + public void sendMessage(ChatAvatar sender, String message, OutOfBandPackage oob, PlayerManager playerManager) { + ChatRoomMessage chatRoomMessage = new ChatRoomMessage(sender, getId(), message, oob); + + for (ChatAvatar avatar : members) { + Player player = playerManager.getPlayerFromNetworkId(avatar.getNetworkId()); + if (player == null) + return; + + player.sendPacket(chatRoomMessage); + } + } + + @Override + public void decode(ByteBuffer data) {} + + @Override + public byte[] encode() { + if (!modified && data != null) + return data; + + int avatarIdSize = 0; // The encode method for ChatAvatar saves the encode result if the class was modified/null data + for (ChatAvatar moderator : moderators) { + avatarIdSize += moderator.encode().length; + } + + for (ChatAvatar invitee : invited) { + avatarIdSize += invitee.encode().length; + } + avatarIdSize += owner.encode().length + creator.encode().length; + + ByteBuffer bb = ByteBuffer.allocate(23 + path.length() + (title.length() * 2) + avatarIdSize).order(ByteOrder.LITTLE_ENDIAN); + bb.putInt(id); + bb.putInt(type); + bb.put(muted ? (byte) 1 : (byte) 0); + Packet.addAscii(bb, path); + bb.put(owner.encode()); + bb.put(creator.encode()); + Packet.addUnicode(bb, title); + bb.putInt(moderators.size()); + for (ChatAvatar moderator : moderators) { + bb.put(moderator.encode()); + } + bb.putInt(invited.size()); + for (ChatAvatar invitee : invited) { + bb.put(invitee.encode()); + } + + data = bb.array(); + modified = false; + + return data; + } + + @Override + public String toString() { + return "ChatRoom{" + + "id=" + id + + ", path='" + path + '\'' + + ", title='" + title + '\'' + + '}'; + } +} diff --git a/src/resources/commands/callbacks/WaypointCmdCallback.java b/src/resources/commands/callbacks/WaypointCmdCallback.java index b34753d8d..c7b087fda 100644 --- a/src/resources/commands/callbacks/WaypointCmdCallback.java +++ b/src/resources/commands/callbacks/WaypointCmdCallback.java @@ -162,7 +162,7 @@ public class WaypointCmdCallback implements ICmdCallback { } } - if (name == null) + if (name == null || name.isEmpty()) name = "Waypoint"; WaypointObject waypoint = createWaypoint(galacticManager.getObjectManager(), color, name, location); diff --git a/src/resources/network/BaselineBuilder.java b/src/resources/network/BaselineBuilder.java index e412e32bf..2caa8f95f 100644 --- a/src/resources/network/BaselineBuilder.java +++ b/src/resources/network/BaselineBuilder.java @@ -165,5 +165,8 @@ public class BaselineBuilder { public interface Encodable extends Serializable { byte [] encode(); } - + + public interface Decodable { + void decode(ByteBuffer data); + } } diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java index 0cfed02b9..ae7a53e51 100644 --- a/src/resources/objects/player/PlayerObject.java +++ b/src/resources/objects/player/PlayerObject.java @@ -44,13 +44,16 @@ import resources.player.PlayerFlags; import utilities.MathUtils; import utilities.Encoder.StringType; +import java.util.LinkedList; import java.util.List; public class PlayerObject extends IntangibleObject { private static final long serialVersionUID = 1L; - private String biography = ""; + private String biography = ""; + private List joinedChannels = new LinkedList<>(); + // PLAY 03 private SWGBitSet flagsList = new SWGBitSet(BaselineType.PLAY, 3, 5); private SWGBitSet profileFlags = new SWGBitSet(BaselineType.PLAY, 3, 6); @@ -384,7 +387,11 @@ public class PlayerObject extends IntangibleObject { flagsList.flip(flag.getFlag()); flagsList.sendDeltaMessage(this); } - + + public List getJoinedChannels() { + return joinedChannels; + } + private int getProfessionIcon() { switch (profession) { case "entertainer_1a": diff --git a/src/services/chat/ChatService.java b/src/services/chat/ChatService.java index fe84f8833..1e000e28c 100644 --- a/src/services/chat/ChatService.java +++ b/src/services/chat/ChatService.java @@ -31,6 +31,7 @@ import intents.NotifyPlayersPacketIntent; import intents.PlayerEventIntent; import intents.chat.ChatAvatarRequestIntent; import intents.chat.ChatBroadcastIntent; +import intents.chat.ChatRoomUpdateIntent; import intents.chat.PersistentMessageIntent; import intents.chat.SpatialChatIntent; import intents.network.GalacticPacketIntent; @@ -38,23 +39,34 @@ import intents.player.ZonePlayerSwapIntent; import intents.server.ServerStatusIntent; import network.packets.Packet; import network.packets.swg.SWGPacket; -import network.packets.swg.zone.ChatRequestRoomList; import network.packets.swg.zone.chat.ChatDeletePersistentMessage; +import network.packets.swg.zone.chat.ChatEnterRoomById; import network.packets.swg.zone.chat.ChatFriendsListUpdate; import network.packets.swg.zone.chat.ChatInstantMessageToCharacter; import network.packets.swg.zone.chat.ChatInstantMessageToClient; import network.packets.swg.zone.chat.ChatOnAddFriend; import network.packets.swg.zone.chat.ChatOnChangeFriendStatus; +import network.packets.swg.zone.chat.ChatOnEnteredRoom; import network.packets.swg.zone.chat.ChatOnGetFriendsList; import network.packets.swg.zone.chat.ChatOnSendInstantMessage; import network.packets.swg.zone.chat.ChatOnSendPersistentMessage; +import network.packets.swg.zone.chat.ChatOnSendRoomMessage; import network.packets.swg.zone.chat.ChatPersistentMessageToClient; import network.packets.swg.zone.chat.ChatPersistentMessageToServer; +import network.packets.swg.zone.chat.ChatQueryRoom; +import network.packets.swg.zone.chat.ChatQueryRoomResults; import network.packets.swg.zone.chat.ChatRequestPersistentMessage; +import network.packets.swg.zone.chat.ChatRequestRoomList; +import network.packets.swg.zone.chat.ChatSendToRoom; import network.packets.swg.zone.chat.ChatSystemMessage; import network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; +import network.packets.swg.zone.insertion.ChatRoomList; import network.packets.swg.zone.object_controller.SpatialChat; +import resources.Galaxy; import resources.Terrain; +import resources.chat.ChatAvatar; +import resources.chat.ChatResult; +import resources.chat.ChatRoom; import resources.collections.SWGList; import resources.control.Intent; import resources.control.Service; @@ -63,6 +75,7 @@ import resources.encodables.ProsePackage; import resources.encodables.player.Mail; import resources.objects.SWGObject; import resources.objects.player.PlayerObject; +import resources.player.AccessLevel; import resources.player.Player; import resources.player.PlayerState; import resources.server_info.CachedObjectDatabase; @@ -71,18 +84,33 @@ import resources.server_info.ObjectDatabase.Traverser; import services.player.PlayerManager; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class ChatService extends Service { - + + // Array of only the valid terrains in relation to zones able to use planetary-based chat + private static final Terrain[] terrains = new Terrain[] { + 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; + public ChatService() { + roomMap = new HashMap<>(); mails = new CachedObjectDatabase<>("odb/mails.db"); maxMailId = 1; + maxChatRoomId = 1; + messages = new ConcurrentHashMap<>(); } @Override @@ -95,6 +123,7 @@ public class ChatService extends Service { registerForIntent(ServerStatusIntent.TYPE); registerForIntent(ChatAvatarRequestIntent.TYPE); registerForIntent(ZonePlayerSwapIntent.TYPE); + registerForIntent(ChatRoomUpdateIntent.TYPE); mails.load(); mails.traverse(new Traverser() { @Override @@ -138,6 +167,17 @@ 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; } } @@ -145,6 +185,7 @@ public class ChatService extends Service { Player player = intent.getPlayerManager().getPlayerFromNetworkId(intent.getNetworkId()); if (player == null) return; + Packet p = intent.getPacket(); if (p instanceof SWGPacket) processSwgPacket(intent.getPlayerManager(), player, intent.getGalaxy().getName(), (SWGPacket) p); @@ -152,9 +193,23 @@ public class ChatService extends Service { private void processSwgPacket(PlayerManager pm, Player player, String galaxyName, 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) { + ChatEnterRoomById enterRoomById = (ChatEnterRoomById) p; + enterChatChannel(player, enterRoomById.getRoomId(), enterRoomById.getSequence()); + } + 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, (ChatRequestRoomList) p); + handleChatRoomListRequest(player); break; case CHAT_INSTANT_MESSAGE_TO_CHARACTER: if (p instanceof ChatInstantMessageToCharacter) @@ -176,13 +231,19 @@ public class ChatService extends Service { break; } } - + private void handlePlayerEventIntent(PlayerEventIntent intent) { switch (intent.getEvent()) { + case PE_ZONE_IN: + // TODO: Add players to proper planet chat room + break; case PE_FIRST_ZONE: Player player = intent.getPlayer(); sendPersistentMessageHeaders(player, intent.getGalaxy()); updateChatAvatarStatus(player, intent.getGalaxy(), true); + enterJoinedChatChannels(player); + enterChatChannel(intent.getPlayer(), "SWG.Localhost.Tatooine.Planet"); + enterChatChannel(intent.getPlayer(), "SWG.Localhost.Tatooine.system"); break; case PE_LOGGED_OUT: if (intent.getPlayer() == null || intent.getPlayer().getCreatureObject() == null) @@ -232,6 +293,265 @@ public class ChatService extends Service { } } + /* Chat Rooms */ + + 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) { + // TODO: Filter out private rooms + ChatRoomList response = new ChatRoomList(roomMap.values()); + 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) { + if (player == null || player.getPlayerState() != PlayerState.ZONED_IN) + return; + + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; + + for (Integer id : ghost.getJoinedChannels()) { + enterChatChannel(player, id); + } + } + + /** + * 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 + /* // TODO: Disabled until planet chat is automatically entered/removed from the list + List joinedChannels = ghost.getJoinedChannels(); + if (!joinedChannels.contains(room.getId())) + joinedChannels.add(room.getId());*/ + + room.getMembers().add(avatar); + + // 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 + ChatOnEnteredRoom onEnteredRoom = new ChatOnEnteredRoom(avatar, room.getId(), sequence); + onEnteredRoom.setResult(result.getCode()); // ChatResult.SUCCESS at this point + player.sendPacket(onEnteredRoom); + + PlayerManager manager = player.getPlayerManager(); + // Notify everyone that a player entered the room + for (ChatAvatar chatAvatar : room.getMembers()) { + if (chatAvatar.equals(avatar)) + continue; + + Player member = manager.getPlayerFromNetworkId(chatAvatar.getNetworkId()); + if (member == null || member.getPlayerState() != PlayerState.ZONED_IN) + continue; + // TODO: Should sequence be set to 0 for other members? + member.sendPacket(onEnteredRoom); + } + + System.out.println(avatar + " has entered room " + room); + } + + 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, int id) { + enterChatChannel(player, id, 0); + } + + public void enterChatChannel(Player player, String path) { + for (ChatRoom room : roomMap.values()) { + if (room.getPath().equals(path)) { + enterChatChannel(player, room, 0); + return; + } + } + System.out.println("Couldn't find room with path " + path); + } + + /** + * 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); + + 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 + Character.toUpperCase(terrain.getName().charAt(0)) + terrain.getName().substring(1) + "."; + 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) { + ChatAvatar systemAvatar = new ChatAvatar(-1, "System", galaxy.getName()); + createSystemChannels(galaxy.getName()); + /** 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); + } + + /* Friends List */ + private void handleRequestFriendList(Player player) { PlayerObject ghost = player.getPlayerObject(); if (ghost == null) @@ -290,10 +610,6 @@ public class ChatService extends Service { new ChatBroadcastIntent(player, new ProsePackage("@cmnty:friend_added", "TT", target)).broadcast(); } - private void handleChatRoomListRequest(Player player, ChatRequestRoomList request) { - - } - private void handleSpatialChat(SpatialChatIntent i) { Player sender = i.getPlayer(); SWGObject actor = sender.getCreatureObject(); @@ -329,7 +645,9 @@ public class ChatService extends Service { receiver.sendPacket(new ChatInstantMessageToClient(request.getGalaxy(), strSender, request.getMessage())); } - + + /* Mails */ + private void handleSendPersistentMessage(PlayerManager playerMgr, Player sender, String galaxy, ChatPersistentMessageToServer request) { String recipientStr = request.getRecipient().toLowerCase(Locale.ENGLISH); @@ -501,7 +819,7 @@ public class ChatService extends Service { private void deletePersistentMessage(int mailId) { mails.remove(mailId); } - + private enum MailFlagType { FULL_MESSAGE, HEADER_ONLY diff --git a/src/services/galaxy/GalacticManager.java b/src/services/galaxy/GalacticManager.java index 17689ef4b..ac78b52d0 100644 --- a/src/services/galaxy/GalacticManager.java +++ b/src/services/galaxy/GalacticManager.java @@ -33,6 +33,7 @@ import intents.network.InboundPacketIntent; import resources.Galaxy; import resources.control.Intent; import resources.control.Manager; +import services.chat.ChatService; import services.objects.ObjectManager; import services.player.PlayerManager; @@ -43,6 +44,7 @@ public class GalacticManager extends Manager { private ObjectManager objectManager; private PlayerManager playerManager; private GameManager gameManager; + private ChatService chatService; private Intent prevPacketIntent; private Galaxy galaxy; @@ -51,16 +53,19 @@ public class GalacticManager extends Manager { objectManager = new ObjectManager(); playerManager = new PlayerManager(); gameManager = new GameManager(); + chatService = new ChatService(); prevPacketIntent = null; addChildService(objectManager); addChildService(playerManager); addChildService(gameManager); + addChildService(chatService); } @Override public boolean initialize() { registerForIntent(InboundPacketIntent.TYPE); + chatService.initializeGalaxyChannels(galaxy); return super.initialize(); } diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 90a6c6fa1..5b29af5a7 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -33,21 +33,18 @@ import services.commands.CommandService; import services.sui.SuiService; public class GameManager extends Manager { - - private ChatService chatService; + private CommandService commandService; private ConnectionService connectionService; private SuiService suiService; private WeatherService weatherService; public GameManager() { - chatService = new ChatService(); commandService = new CommandService(); connectionService = new ConnectionService(); suiService = new SuiService(); weatherService = new WeatherService(); - - addChildService(chatService); + addChildService(commandService); addChildService(connectionService); addChildService(suiService);