mirror of
https://bitbucket.org/projectswg/cucore.git
synced 2026-10-15 07:13:00 -04:00
Changed ChatService to a manager and moved chat room functionality to ChatRoomService under ChatManager
NotifyPlayersPacketIntent can now accept a list of networkId's to notify players with a packet
This commit is contained in:
@@ -0,0 +1,458 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com
|
||||
*
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies.
|
||||
* Our goal is to create an emulator which will provide a server for players to
|
||||
* continue playing a game similar to the one they used to play. We are basing
|
||||
* it on the final publish of the game prior to end-game events.
|
||||
*
|
||||
* This file is part of Holocore.
|
||||
*
|
||||
* --------------------------------------------------------------------------------
|
||||
*
|
||||
* Holocore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Holocore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
package services.chat;
|
||||
|
||||
import intents.chat.ChatRoomUpdateIntent;
|
||||
import intents.network.GalacticPacketIntent;
|
||||
import network.packets.Packet;
|
||||
import network.packets.swg.SWGPacket;
|
||||
import network.packets.swg.zone.chat.ChatCreateRoom;
|
||||
import network.packets.swg.zone.chat.ChatEnterRoomById;
|
||||
import network.packets.swg.zone.chat.ChatOnCreateRoom;
|
||||
import network.packets.swg.zone.chat.ChatOnEnteredRoom;
|
||||
import network.packets.swg.zone.chat.ChatOnLeaveRoom;
|
||||
import network.packets.swg.zone.chat.ChatOnSendRoomMessage;
|
||||
import network.packets.swg.zone.chat.ChatQueryRoom;
|
||||
import network.packets.swg.zone.chat.ChatQueryRoomResults;
|
||||
import network.packets.swg.zone.chat.ChatRemoveAvatarFromRoom;
|
||||
import network.packets.swg.zone.chat.ChatRequestRoomList;
|
||||
import network.packets.swg.zone.chat.ChatSendToRoom;
|
||||
import network.packets.swg.zone.insertion.ChatRoomList;
|
||||
import resources.Terrain;
|
||||
import resources.chat.ChatAvatar;
|
||||
import resources.chat.ChatResult;
|
||||
import resources.chat.ChatRoom;
|
||||
import resources.control.Intent;
|
||||
import resources.control.Service;
|
||||
import resources.objects.player.PlayerObject;
|
||||
import resources.player.AccessLevel;
|
||||
import resources.player.Player;
|
||||
import services.player.PlayerManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author Waverunner
|
||||
*/
|
||||
public class ChatRoomService extends Service {
|
||||
// Array of only the valid terrains in relation to zones able to use planetary-based chat
|
||||
private static final List<Terrain> terrains = Arrays.asList(
|
||||
Terrain.CORELLIA, Terrain.DANTOOINE, Terrain.DATHOMIR, Terrain.ENDOR, Terrain.KASHYYYK, Terrain.LOK,
|
||||
Terrain.MUSTAFAR, Terrain.NABOO, Terrain.RORI, Terrain.TATOOINE, Terrain.TALUS, Terrain.YAVIN4
|
||||
);
|
||||
private int maxChatRoomId;
|
||||
private Map<Integer, ChatRoom> roomMap;
|
||||
// Map to keep track of each player's recent message for a room to prevent duplicates from client
|
||||
private Map<Long, Map<Integer, Integer>> messages;
|
||||
|
||||
public ChatRoomService() {
|
||||
roomMap = new ConcurrentHashMap<>();
|
||||
messages = new ConcurrentHashMap<>();
|
||||
maxChatRoomId = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
// TODO: Load up persistent channels
|
||||
registerForIntent(ChatRoomUpdateIntent.TYPE);
|
||||
registerForIntent(GalacticPacketIntent.TYPE);
|
||||
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIntentReceived(Intent i) {
|
||||
switch(i.getType()) {
|
||||
case ChatRoomUpdateIntent.TYPE:
|
||||
processChatRoomUpdateIntent((ChatRoomUpdateIntent) i);
|
||||
break;
|
||||
case GalacticPacketIntent.TYPE:
|
||||
processPacket((GalacticPacketIntent) i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processPacket(GalacticPacketIntent intent) {
|
||||
Player player = intent.getPlayerManager().getPlayerFromNetworkId(intent.getNetworkId());
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
Packet p = intent.getPacket();
|
||||
if (p instanceof SWGPacket)
|
||||
processSwgPacket(player, (SWGPacket) p);
|
||||
}
|
||||
|
||||
private void processSwgPacket(Player player, SWGPacket p) {
|
||||
switch (p.getPacketType()) {
|
||||
case CHAT_QUERY_ROOM:
|
||||
if (p instanceof ChatQueryRoom) handleChatQueryRoom(player, (ChatQueryRoom) p);
|
||||
break;
|
||||
case CHAT_ENTER_ROOM_BY_ID: {
|
||||
if (!(p instanceof ChatEnterRoomById)) return;
|
||||
ChatEnterRoomById enterRoomById = (ChatEnterRoomById) p;
|
||||
enterChatChannel(player, enterRoomById.getRoomId(), enterRoomById.getSequence());
|
||||
break; }
|
||||
case CHAT_REMOVE_AVATAR_FROM_ROOM:
|
||||
if (p instanceof ChatRemoveAvatarFromRoom)
|
||||
leaveChatChannel(player, ((ChatRemoveAvatarFromRoom) p).getPath());
|
||||
break;
|
||||
case CHAT_SEND_TO_ROOM:
|
||||
if (p instanceof ChatSendToRoom) handleChatSendToRoom(player, (ChatSendToRoom) p);
|
||||
break;
|
||||
case CHAT_REQUEST_ROOM_LIST:
|
||||
if (p instanceof ChatRequestRoomList)
|
||||
handleChatRoomListRequest(player);
|
||||
break;
|
||||
case CHAT_CREATE_ROOM:
|
||||
if (p instanceof ChatCreateRoom) handleChatCreateRoom(player, (ChatCreateRoom) p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processChatRoomUpdateIntent(ChatRoomUpdateIntent i) {
|
||||
switch(i.getUpdateType()) {
|
||||
case CREATE: createRoom(i.getAvatar(), i.isPublic(), i.getPath(), i.getTitle()); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chat Rooms */
|
||||
|
||||
private void handleChatCreateRoom(Player player, ChatCreateRoom p) {
|
||||
String path = p.getRoomName();
|
||||
String title = p.getRoomTitle();
|
||||
ChatRoom room = getRoom(path);
|
||||
|
||||
ChatResult result = ChatResult.SUCCESS;
|
||||
if (room != null)
|
||||
result = ChatResult.ROOM_ALREADY_EXISTS;
|
||||
|
||||
if (result == ChatResult.SUCCESS) {
|
||||
room = createRoom(ChatAvatar.getFromPlayer(player), p.isPublic(), path, title);
|
||||
room.setMuted(p.isModerated());
|
||||
}
|
||||
|
||||
player.sendPacket(new ChatOnCreateRoom(result.getCode(), room, p.getSequence()));
|
||||
}
|
||||
|
||||
private void handleChatSendToRoom(Player player, ChatSendToRoom p) {
|
||||
ChatResult result = ChatResult.SUCCESS;
|
||||
|
||||
ChatRoom room = getRoom(p.getRoomId());
|
||||
if (room == null)
|
||||
result = ChatResult.ROOM_INVALID_ID;
|
||||
|
||||
if (result != ChatResult.SUCCESS) {
|
||||
player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!incrementMessageCounter(player.getNetworkId(), room.getId(), p.getSequence()))
|
||||
return;
|
||||
|
||||
ChatAvatar avatar = ChatAvatar.getFromPlayer(player);
|
||||
|
||||
result = room.canSendMessage(avatar);
|
||||
|
||||
// TODO: Check length of messages -- Result 16 is used for too long message
|
||||
|
||||
player.sendPacket(new ChatOnSendRoomMessage(result.getCode(), p.getSequence()));
|
||||
|
||||
if (result == ChatResult.SUCCESS)
|
||||
room.sendMessage(avatar, p.getMessage(), p.getOutOfBandPackage(), player.getPlayerManager());
|
||||
}
|
||||
|
||||
private void handleChatQueryRoom(Player player, ChatQueryRoom p) {
|
||||
ChatRoom room = getRoom(p.getRoomPath()); // No result code is sent for queries
|
||||
if (room == null)
|
||||
return;
|
||||
|
||||
player.sendPacket(new ChatQueryRoomResults(room, p.getSequence()));
|
||||
}
|
||||
|
||||
private void handleChatRoomListRequest(Player player) {
|
||||
ChatAvatar avatar = ChatAvatar.getFromPlayer(player);
|
||||
|
||||
List<ChatRoom> rooms = new ArrayList<>();
|
||||
for (ChatRoom chatRoom : roomMap.values()) {
|
||||
if (!chatRoom.isPublic() && !chatRoom.getInvited().contains(avatar) && !chatRoom.getOwner().equals(avatar))
|
||||
continue;
|
||||
rooms.add(chatRoom);
|
||||
}
|
||||
|
||||
ChatRoomList response = new ChatRoomList(rooms);
|
||||
player.sendPacket(response);
|
||||
}
|
||||
|
||||
private void sendOnEnteredChatRoom(Player player, ChatAvatar avatar, ChatResult result, int id, int sequence) {
|
||||
ChatOnEnteredRoom onEnteredRoom = new ChatOnEnteredRoom(avatar, id, sequence);
|
||||
onEnteredRoom.setResult(result.getCode());
|
||||
player.sendPacket(onEnteredRoom);
|
||||
}
|
||||
|
||||
public void enterChatChannels(Player player, List<String> channels) {
|
||||
for (String s : channels) {
|
||||
enterChatChannel(player, s);
|
||||
}
|
||||
}
|
||||
|
||||
public void enterPlanetaryChatChannels(Player player) {
|
||||
PlayerObject ghost = player.getPlayerObject();
|
||||
if (ghost == null)
|
||||
return;
|
||||
|
||||
// Leave old zone-only chat channels
|
||||
String planetEndPath = ".Planet";
|
||||
for (String channel : ghost.getJoinedChannels()) {
|
||||
if (channel.endsWith(planetEndPath)) {
|
||||
leaveChatChannel(player, channel);
|
||||
} else {
|
||||
// Better way of doing this?
|
||||
String[] split = channel.split("\\.");
|
||||
if (split.length == 3 && split[2].equals("system"))
|
||||
leaveChatChannel(player, channel);
|
||||
}
|
||||
}
|
||||
|
||||
Terrain terrain = player.getCreatureObject().getLocation().getTerrain();
|
||||
|
||||
// Enter the new zone-only chat channels
|
||||
if (!terrains.contains(terrain))
|
||||
return;
|
||||
|
||||
String planetPath = "SWG." + player.getGalaxyName() + "." + terrain.getNameCapitalized() + ".";
|
||||
enterChatChannel(player, planetPath + "Planet");
|
||||
enterChatChannel(player, planetPath + "system");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to join the specified chat channel
|
||||
* @param player Player joining the chat channel
|
||||
* @param room Chat room to enter
|
||||
*/
|
||||
public void enterChatChannel(Player player, ChatRoom room, int sequence) {
|
||||
ChatAvatar avatar = ChatAvatar.getFromPlayer(player);
|
||||
|
||||
PlayerObject ghost = player.getPlayerObject();
|
||||
if (ghost == null) {
|
||||
sendOnEnteredChatRoom(player, avatar, ChatResult.NONE, room.getId(), sequence);
|
||||
return;
|
||||
}
|
||||
|
||||
ChatResult result = room.canJoinRoom(avatar);
|
||||
if (result != ChatResult.SUCCESS && player.getAccessLevel() != AccessLevel.PLAYER) {
|
||||
sendOnEnteredChatRoom(player, avatar, result, room.getId(), sequence);
|
||||
return;
|
||||
}
|
||||
// TODO: Check if player is appropriate faction for the room (Rebel and imperial chat rooms)
|
||||
|
||||
// Server-based list so we can join chat channels automatically
|
||||
ghost.addJoinedChannel(room.getPath());
|
||||
|
||||
// Re-send the player the room list with just this room as it could have been private/hidden
|
||||
// This also "refreshes" the client, not sending this will cause a Chat channel unavailable message.
|
||||
ChatRoomList roomList = new ChatRoomList(room);
|
||||
player.sendPacket(roomList);
|
||||
|
||||
// Notify players of success, it's ChatResult.SUCCESS at this point
|
||||
player.sendPacket(new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), sequence));
|
||||
|
||||
PlayerManager manager = player.getPlayerManager();
|
||||
// Notify everyone that a player entered the room
|
||||
room.sendPacketToMembers(manager, new ChatOnEnteredRoom(avatar, result.getCode(), room.getId(), 0));
|
||||
|
||||
room.getMembers().add(avatar);
|
||||
}
|
||||
|
||||
public void enterChatChannel(Player player, int id, int sequence) {
|
||||
ChatRoom room = getRoom(id);
|
||||
if (room == null) {
|
||||
sendOnEnteredChatRoom(player, ChatAvatar.getFromPlayer(player), ChatResult.NONE, id, sequence);
|
||||
return;
|
||||
}
|
||||
enterChatChannel(player, room, sequence);
|
||||
}
|
||||
|
||||
public void enterChatChannel(Player player, String path) {
|
||||
for (ChatRoom room : roomMap.values()) {
|
||||
if (room.getPath().equals(path)) {
|
||||
enterChatChannel(player, room, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
System.err.println("Tried to join a room with a path that does not exist: " + path);
|
||||
}
|
||||
|
||||
public void leaveChatChannel(Player player, ChatRoom room, int sequence) {
|
||||
ChatAvatar avatar = ChatAvatar.getFromPlayer(player);
|
||||
|
||||
PlayerObject ghost = player.getPlayerObject();
|
||||
if (ghost == null)
|
||||
return; // ChatOnLeaveRoom doesn't do anything other than for a ChatResult.SUCCESS, so no need to send a fail
|
||||
|
||||
if (!room.getMembers().remove(avatar) && !ghost.removeJoinedChannel(room.getPath()))
|
||||
return;
|
||||
|
||||
player.sendPacket(new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), sequence));
|
||||
|
||||
room.sendPacketToMembers(player.getPlayerManager(), new ChatOnLeaveRoom(avatar, ChatResult.SUCCESS.getCode(), room.getId(), 0));
|
||||
}
|
||||
|
||||
public void leaveChatChannel(Player player, String path) {
|
||||
for (ChatRoom chatRoom : roomMap.values()) {
|
||||
if (chatRoom.getPath().equals(path))
|
||||
leaveChatChannel(player, chatRoom, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a new, non-persistent, chat room with the specified address path.
|
||||
* @param creator Room creator who will also become the owner of this room
|
||||
* @param isPublic Determines if the room should be publicly displayed in the channel listing
|
||||
* @param path Address for the channel (Ex: SWG.serverName.Imperial)
|
||||
* @param title Descriptive name of the chat channel (Ex: Imperial chat for this galaxy)
|
||||
* @return {@link ChatRoom}
|
||||
*/
|
||||
public ChatRoom createRoom(ChatAvatar creator, boolean isPublic, String path, String title) {
|
||||
if (path.isEmpty() || path.endsWith("."))
|
||||
return null;
|
||||
|
||||
String base = "SWG." + creator.getGalaxy();
|
||||
if (!path.startsWith(base) || path.equals(base))
|
||||
return null;
|
||||
|
||||
// All paths should have parents, lets validate to make sure they exist first. Create them if they don't.
|
||||
int lastIndex = path.lastIndexOf(".");
|
||||
if (lastIndex != -1) {
|
||||
String parentPath = path.substring(0, lastIndex);
|
||||
if (getRoom(parentPath) == null) {
|
||||
createRoom(creator, isPublic, parentPath, "");
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom room = new ChatRoom();
|
||||
int id = maxChatRoomId;
|
||||
maxChatRoomId++;
|
||||
|
||||
room.setId(id);
|
||||
room.setOwner(creator);
|
||||
room.setCreator(creator);
|
||||
room.setIsPublic(isPublic);
|
||||
room.setPath(path);
|
||||
room.setTitle(title);
|
||||
|
||||
roomMap.put(id, room);
|
||||
return room;
|
||||
}
|
||||
|
||||
public boolean destroyRoom(ChatRoom room) {
|
||||
// Remove existing, online, members from the room
|
||||
// Remove room from map and database
|
||||
// Remove roompath from joinedChannels list if player tried to join an invalid room
|
||||
return false;
|
||||
}
|
||||
|
||||
public void createSystemChannels(String galaxy) {
|
||||
/** Channel Notes
|
||||
* Group channels: created by System
|
||||
* - SWG.serverName.group.GroupObjectId.GroupChat title = GroupId
|
||||
* Guild channels: created by System
|
||||
* - SWG.serverName.guild.GuildId.GuildChat title = GuildId
|
||||
* City channels: created by System
|
||||
* - SWG.serverName.city.CityId.CityChat title = CityId
|
||||
*/
|
||||
|
||||
ChatAvatar systemAvatar = ChatAvatar.getSystemAvatar(galaxy);
|
||||
// TODO: Move to server datatable file (SDF): Room (s) | Title (s) | Private (b)
|
||||
String basePath = "SWG." + galaxy + ".";
|
||||
createRoom(systemAvatar, true, basePath + "Galaxy", "public chat for the whole galaxy, cannot create rooms here");
|
||||
createRoom(systemAvatar, true, basePath + "system", "system messages for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Chat", "public chat for this galaxy, can create rooms here");
|
||||
createRoom(systemAvatar, true, basePath + "Imperial", "Imperial chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "ImperialWarRoom", "Imperial war room chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Rebel", "Rebel chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "RebelWarRoom", "Rebel war room chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "BountyHunter", "Bounty Hunter chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Commando", "Commando chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Entertainer", "Entertainer chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "ForceSensitive", "Force Sensitive chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Medic", "Medic chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Officer", "Officer chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Pilot", "Pilot chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Politician", "Politician chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Smuggler", "Smuggler chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Spy", "Spy chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Trader", "Trader chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "BeastMastery", "Beast Mastery chat for this galaxy");
|
||||
createRoom(systemAvatar, true, basePath + "Auction", "Auction chat for this galaxy");
|
||||
createPlanetChannels(systemAvatar, basePath);
|
||||
}
|
||||
|
||||
private void createPlanetChannels(ChatAvatar systemAvatar, String basePath) {
|
||||
// TODO: Move planets to a server datatable file (SDF): Planet (s)
|
||||
for (Terrain terrain : terrains) {
|
||||
String path = basePath + terrain.getNameCapitalized() + ".";
|
||||
createRoom(systemAvatar, true, path + "Planet", "public chat for this planet, cannot create rooms here");
|
||||
createRoom(systemAvatar, true, path + "system", "system messages for this planet, cannot create rooms here");
|
||||
createRoom(systemAvatar, true, path + "Chat", "public chat for this planet, can create rooms here");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean incrementMessageCounter(long networkId, int roomId, int messageId) {
|
||||
if (networkId == -1) // System ChatAvatar uses -1 for networkId
|
||||
return true;
|
||||
|
||||
Map<Integer, Integer> messageHistory = messages.get(networkId);
|
||||
if (messageHistory != null) {
|
||||
if (messageHistory.containsKey(roomId) && messageHistory.get(roomId) == messageId)
|
||||
return false;
|
||||
} else {
|
||||
messageHistory = new HashMap<>();
|
||||
messages.put(networkId, messageHistory);
|
||||
}
|
||||
messageHistory.put(roomId, messageId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public ChatRoom getRoom(String path) {
|
||||
for (ChatRoom room : roomMap.values()) {
|
||||
if (room.getPath().equals(path))
|
||||
return room;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChatRoom getRoom(int roomId) {
|
||||
return roomMap.get(roomId);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user