package org.pokenet.server.network;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.pokenet.server.GameServer;
import org.pokenet.server.backend.ItemProcessor;
import org.pokenet.server.backend.entity.PlayerChar;
import org.pokenet.server.backend.entity.PlayerChar.RequestType;
import org.pokenet.server.backend.entity.Positionable.Direction;
import org.pokenet.server.battle.BattleTurn;
import org.pokenet.server.battle.impl.PvPBattleField;
import org.pokenet.server.battle.impl.WildBattleField;
import org.pokenet.server.network.message.ItemMessage;
import org.pokenet.server.network.message.PokenetMessage;
import org.pokenet.server.network.message.RequestMessage;
/**
* Handles packets received from players over TCP
* @author shadowkanji
*
*/
public class TcpProtocolHandler extends IoHandlerAdapter {
private static HashMap<String, PlayerChar> m_players;
private LoginManager m_loginManager;
private LogoutManager m_logoutManager;
private RegistrationManager m_regManager;
/**
* Constructor
* @param login
* @param logout
*/
public TcpProtocolHandler(LoginManager login, LogoutManager logout) {
m_loginManager = login;
m_logoutManager = logout;
m_regManager = new RegistrationManager();
m_regManager.start();
}
static {
m_players = new HashMap<String, PlayerChar>();
}
@Override
public void sessionCreated(IoSession session) {
//Tell the client which revision the server is on
session.write("R" + GameServer.REVISION);
}
/**
* Handles any exceptions involving a player's session
*/
public void exceptionCaught(IoSession session, Throwable t)
throws Exception {
/*
* Attempt to disconnect and logout the player (save their data)
*/
try {
PlayerChar p = (PlayerChar) session.getAttribute("player");
if(p != null) {
if (p.isBattling())
p.lostBattle();
m_logoutManager.queuePlayer(p);
GameServer.getServiceManager().getMovementService().removePlayer(p.getName());
m_players.remove(p);
}
if(!(t instanceof IOException) || session.isConnected() || !session.isClosing())
session.close(true);
} catch (Exception e) {
e.printStackTrace();
}
t.printStackTrace();
}
/**
* Once the server receives a packet from the client, this method is run.
* @param IoSession session - A client session
* @param Object msg - The packet received from the client
*/
public void messageReceived(IoSession session, Object msg) throws Exception {
String message = (String) msg;
String [] details;
if (!message.equals("L") && !message.equals("R") && !message.equals("D") && !message.equals("U")) {
System.out.println(message);
}
if(session.getAttribute("player") == null) {
/*
* The player hasn't been logged in, only allow login and registration packets
*/
switch(message.charAt(0)) {
case 'l':
//Login packet
details = message.substring(1).split(",");
m_loginManager.queuePlayer(session, details[0], details[1]);
break;
case 'r':
//Registration packet
m_regManager.queueRegistration(session, message.substring(1));
break;
case 'c':
//Change password packet
details = message.substring(1).split(",");
m_loginManager.queuePasswordChange(session, details[0], details[1], details[2]);
break;
case 'f':
//Force login
details = message.substring(1).split(",");
m_loginManager.queuePlayer(session, details[0], details[1]);
break;
}
} else {
/*
* Player is logged in, allow interaction with their player object
*/
PlayerChar p = (PlayerChar) session.getAttribute("player");
p.lastPacket = System.currentTimeMillis();
switch(message.charAt(0)) {
case 'U':
//Move up
p.queueMovement(Direction.Up);
break;
case 'D':
//Move down
p.queueMovement(Direction.Down);
break;
case 'L':
//Move left
p.queueMovement(Direction.Left);
break;
case 'R':
//Move right
p.queueMovement(Direction.Right);
break;
case 'P':
//Pokemon interaction
int pokemonIndex = 0;
String move;
switch(message.charAt(1)) {
case 'm':
//Player is allowing move to be learned
pokemonIndex = Integer.parseInt(String.valueOf(message.charAt(2)));
int moveIndex = Integer.parseInt(String.valueOf(message.charAt(3)));
move = message.substring(4);
if(move != null && !move.equalsIgnoreCase("") &&
p.getParty()[pokemonIndex] != null) {
if(p.getParty()[pokemonIndex].getMovesLearning().contains(move)) {
p.getParty()[pokemonIndex].learnMove(moveIndex, move);
p.updateClientPP(pokemonIndex, moveIndex);
}
}
break;
case 'M':
//Player is not allowing the move to be learned
pokemonIndex = Integer.parseInt(String.valueOf(message.charAt(2)));
move = message.substring(3);
if(p.getParty()[pokemonIndex] != null) {
if(p.getParty()[pokemonIndex].getMovesLearning().contains(move)) {
p.getParty()[pokemonIndex].getMovesLearning().remove(move);
}
}
break;
case 'e':
//Evolution response
pokemonIndex = Integer.parseInt(String.valueOf(message.charAt(3)));
if(p.getParty()[pokemonIndex] != null) {
switch(message.charAt(2)) {
case '0':
//Cancel evolution
p.getParty()[pokemonIndex].evolutionResponse(false, p);
break;
case '1':
//Allow evolution
p.getParty()[pokemonIndex].evolutionResponse(true, p);
break;
}
}
break;
}
break;
case 's':
//Party swapping
p.swapPokemon(Integer.parseInt(message.substring(1, message.indexOf(','))),
Integer.parseInt(message.substring(message.indexOf(',') + 1)));
break;
case 'S':
//Shop interaction
if(p.isShopping()) {
int item = -1;
switch(message.charAt(1)) {
case 'b':
//Buy items. Sent as SbITEMID,QUANTITY
item = Integer.parseInt(message.substring(2, message.indexOf(',')));
//int q = Integer.parseInt(message.substring(message.indexOf(',') + 1));
p.buyItem(item, 1);
break;
case 's':
//Sell items. Sent as SsITEMID,QUANTITY
item = Integer.parseInt(message.substring(2, message.indexOf(',')));
//int q = Integer.parseInt(message.substring(message.indexOf(',') + 1));
p.sellItem(item, 1);
break;
case 'f':
//Finished shopping
p.setShopping(false);
break;
}
} else if(p.isSpriting()) {
//Sprite changing
int sprite = Integer.parseInt(message.substring(1));
/* Ensure the user buys a visible sprite */
if(sprite > 0 && !GameServer.getServiceManager().
getSpriteList().getUnbuyableSprites().contains(sprite)) {
if(p.getMoney() >= 500) {
p.setMoney(p.getMoney() - 500);
p.updateClientMoney();
p.setSprite(sprite);
p.setSpriting(false);
}
}
}
break;
case 'r':
String player = message.substring(2);
//A request was sent
switch(message.charAt(1)) {
case 'b':
//Battle Request rbUSERNAME
if(m_players.containsKey(player)) {
TcpProtocolHandler.writeMessage(m_players.get(player).getTcpSession(),
new RequestMessage(RequestType.BATTLE, p.getName()));
p.addRequest(player, RequestType.BATTLE);
}
break;
case 't':
//Trade Request rtUSERNAME
if(m_players.containsKey(player)) {
TcpProtocolHandler.writeMessage(m_players.get(player).getTcpSession(),
new RequestMessage(RequestType.TRADE, p.getName()));
p.addRequest(player, RequestType.TRADE);
}
break;
case 'a':
//Request accepted raUSERNAME
if(m_players.containsKey(player)) {
m_players.get(player).requestAccepted(p.getName());
}
break;
case 'c':
//Request declined rcUSERNAME
if(m_players.containsKey(player)) {
m_players.get(player).removeRequest(p.getName());
}
break;
}
break;
case 'B':
//Box interaction
if(p.isBoxing()) {
switch(message.charAt(1)) {
case 'r':
//Requesting info for box number - e.g. Br0
int boxNum = Integer.parseInt(String.valueOf(message.charAt(2)));
if(boxNum >= 0 && boxNum < 9)
p.sendBoxInfo(boxNum);
break;
case 'R':
//Releasing a pokemon from storage - sent as BRBOXNUM,BOXSLOT
details = message.substring(2).split(",");
p.releasePokemon(Integer.parseInt(details[0]), Integer.parseInt(details[1]));
break;
case 's':
//Swap pokemon between box and party - sent as BsBOXNUM,BOXSLOT,PARTYSLOT, e.g.Bs0,1,0
details = message.substring(2).split(",");
p.swapFromBox(Integer.parseInt(details[0]),
Integer.parseInt(details[1]), Integer.parseInt(details[2]));
break;
case 'f':
//Finished with box interfaction
p.setBoxing(false);
break;
}
}
break;
case 'M':
//Moderation
if(message.charAt(1) == 'c') {
p.getTcpSession().write("Cl" + m_players.size() + " players online");
} else if(p.getAdminLevel() > 0) {
try {
switch(message.charAt(1)) {
case 'a':
//Server announcement
for (String s : m_players.keySet()){
m_players.get(s).getTcpSession().write("q" + message.substring(2));
}
break;
case 'l':
//Send an alert
if(p.getAdminLevel()>1)
for (String s : m_players.keySet()){
m_players.get(s).getTcpSession().write("!" + message.substring(2));
}
break;
case 'b':
//Ban player
if(m_players.containsKey(message.substring(2))) {
PlayerChar o = m_players.get(message.substring(2));
MySqlManager m = new MySqlManager();
if(m.connect(GameServer.getDatabaseHost(),
GameServer.getDatabaseUsername(),
GameServer.getDatabasePassword())) {
m.selectDatabase(GameServer.getDatabaseName());
m.query("INSERT INTO pn_bans (ip) VALUE ('" +
o.getIpAddress()
+ "')");
m.close();
}
}
break;
case 'B':
//Unban ip
MySqlManager m = new MySqlManager();
if(m.connect(GameServer.getDatabaseHost(),
GameServer.getDatabaseUsername(),
GameServer.getDatabasePassword())) {
m.selectDatabase(GameServer.getDatabaseName());
m.query("DELETE FROM pn_bans WHERE ip='" +
message.substring(2)
+ "'");
m.close();
}
break;
case 'W':
//Warp to player
if(m_players.containsKey(message.substring(2))) {
PlayerChar o = m_players.get(message.substring(2));
p.setX(o.getX());
p.setY(o.getY());
p.setMap(o.getMap(), null);
}
break;
case 'm':
//Mute player
if(m_players.containsKey(message.substring(2))) {
PlayerChar o = m_players.get(message.substring(2));
o.setMuted(true);
o.getTcpSession().write("!You have been muted.");
}
break;
case 'u':
//Unmute player
if(m_players.containsKey(message.substring(2))) {
PlayerChar o = m_players.get(message.substring(2));
o.setMuted(false);
o.getTcpSession().write("!You have been unmuted.");
}
break;
case 'k':
if(m_players.containsKey(message.substring(2))) {
PlayerChar o = m_players.get(message.substring(2));
o.getTcpSession().write("!You have been kicked from the server.");
o.getTcpSession().close(true);
}
break;
case 'w':
//Change weather on current map
switch(message.charAt(2)) {
case 'n':
//Normal
GameServer.getServiceManager().getTimeService().setForcedWeather(0);
break;
case 'r':
//Rain
GameServer.getServiceManager().getTimeService().setForcedWeather(1);
break;
case 's':
//Snow/Hail
GameServer.getServiceManager().getTimeService().setForcedWeather(2);
break;
case 'f':
//Fog
GameServer.getServiceManager().getTimeService().setForcedWeather(3);
break;
case 'S':
//Sandstorm
GameServer.getServiceManager().getTimeService().setForcedWeather(4);
break;
case 'R':
//Random
GameServer.getServiceManager().getTimeService().setForcedWeather(9);
break;
}
case 's':
if(p.getAdminLevel() == 2) {
GameServer.getServiceManager().stop();
return;
}
break;
case 'n':
//Announce message to server
if(p.getAdminLevel() == 2) {
//TODO: add code?
String mes = message.substring(3);
GameServer.getServiceManager().getNetworkService().getChatManager().
queueLocalChatMessage("<SERVER> " + mes, p.getMapX(), p.getMapY(), p.getLanguage());
}
break;
}
} catch (Exception e) {}
}
break;
case 'b':
//Battle information
if(p.isBattling()) {
BattleTurn turn;
switch(message.charAt(1)) {
case 'm':
//Move selected (bmINDEXOFMOVE)
turn = BattleTurn.getMoveTurn(Integer.parseInt(message.substring(2)));
p.getBattleField().queueMove(p.getBattleId(), turn);
break;
case 's':
//Pokemon switch (bsPARTYINDEX)
int pIndex = Integer.parseInt(message.substring(2));
if(p.getParty()[pIndex] != null) {
if(!p.getParty()[pIndex].isFainted()) {
turn = BattleTurn.getSwitchTurn(pIndex);
p.getBattleField().queueMove(p.getBattleId(), turn);
}
}
break;
case 'r':
//Run
if(p.getBattleField() instanceof WildBattleField) {
((WildBattleField) p.getBattleField()).run();
}
break;
}
}
break;
case 'F':
//Friend list
String friend = message.substring(2);
switch(message.charAt(1)) {
case 'a':
//Add a friend
if(m_players.containsKey(friend));
p.addFriend(message.substring(2));
break;
case 'r':
//Remove a friend
p.removeFriend(message.substring(2));
break;
}
break;
case 'I':
//Use an item, applies inside and outside of battle
details = message.substring(1).split(",");
System.out.println("Item used. "+message);
new Thread(new ItemProcessor(p, details)).start();
break;
case 'i':
//Drop item
if(p.getBag().removeItem(Integer.parseInt(message.substring(1)), 1)) {
TcpProtocolHandler.writeMessage(p.getTcpSession(), new ItemMessage(false,
Integer.parseInt(message.substring(1)), 1));
}
break;
case 'T':
//Trade packets
if(p.isTrading()) {
switch (message.charAt(1)){
case 'o':
//Make an offer ToPOKENUM,MONEYAMOUNT
details = message.substring(2).split(",");
p.getTrade().setOffer(p, Integer.parseInt(String.valueOf(details[0])) ,
Integer.parseInt(String.valueOf(details[1])));
break;
case 't':
//Ready to perform the trade
p.setTradeAccepted(true);
break;
case 'c':
//Cancel the offer
p.cancelTradeOffer();
break;
case 'C':
//Cancel the trade
p.getTrade().endTrade();
break;
}
}
break;
case 'C':
//Chat/Interact
switch(message.charAt(1)) {
case 'l':
//Local chat
String mes = message.substring(2);
if(!p.isMuted())
GameServer.getServiceManager().getNetworkService().getChatManager().
queueLocalChatMessage("<" + p.getName() + "> " + mes, p.getMapX(), p.getMapY(), p.getLanguage());
break;
case 'p':
//Private chat
details = message.substring(2).split(",");
if(m_players.containsKey(details[0])) {
GameServer.getServiceManager().getNetworkService().getChatManager().
queuePrivateMessage(details[1], m_players.get(details[0]).getTcpSession(), p.getName());
}
break;
case 't':
//Start talking
if(!p.isTalking() && !p.isBattling())
p.talkToNpc();
break;
case 'f':
//Finish talking
if(p.isTalking())
p.setTalking(false);
break;
}
break;
}
}
}
/**
* When a user disconnects voluntarily, this method is called
*/
@Override
public void sessionClosed(IoSession session) throws Exception {
/*
* Attempt to save the player's data
*/
try {
PlayerChar p = (PlayerChar) session.getAttribute("player");
if(p != null) {
if(p.isBattling()) {
/* If in PvP battle, the player loses */
if(p.getBattleField() instanceof PvPBattleField) {
((PvPBattleField) p.getBattleField()).disconnect(p.getBattleId());
}
p.setBattleField(null);
p.setBattling(false);
p.lostBattle();
}
/* If trading, end the trade */
if(p.isTrading()) {
p.getTrade().endTrade();
}
m_logoutManager.queuePlayer(p);
GameServer.getServiceManager().getMovementService().removePlayer(p.getName());
m_players.remove(p);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Logs out all players and stops login/logout/registration managers
*/
public void logoutAll() {
m_regManager.stop();
m_loginManager.stop();
/*
* Queue all players to be saved
*/
Iterator<PlayerChar> it = m_players.values().iterator();
PlayerChar p;
while(it.hasNext()) {
p = it.next();
m_logoutManager.queuePlayer(p);
}
/*
* Since the method is called during a server shutdown, wait for all players to be logged out
*/
while(m_logoutManager.getPlayerAmount() > 0);
m_logoutManager.stop();
}
/**
* Adds a player to the player list
* @param p
*/
public static void addPlayer(PlayerChar p) {
synchronized(m_players) {
m_players.put(p.getName(), p);
}
}
/**
* Removes a player from the player list
* @param p
*/
public static void removePlayer(PlayerChar p) {
synchronized(m_players) {
m_players.remove(p.getName());
}
}
/**
* Returns a player
* @param username
* @return
*/
public static PlayerChar getPlayer(String username) {
synchronized(m_players) {
return m_players.get(username);
}
}
/**
* Returns true if the player list contains a player
* @param username
* @return
*/
public static boolean containsPlayer(String username) {
synchronized(m_players) {
return m_players.containsKey(username);
}
}
/**
* Kicks idle players
*/
public static void kickIdlePlayers() {
synchronized(m_players) {
for(PlayerChar p : m_players.values()) {
if(System.currentTimeMillis() - p.lastPacket >= 900000)
p.forceLogout();
}
}
}
/**
* Returns how many players are logged in
* @return
*/
public static int getPlayerCount() {
synchronized(m_players) {
return m_players.keySet().size();
}
}
/**
* Writes the message to the session
* @param session
* @param m
*/
public static void writeMessage(IoSession session, PokenetMessage m) {
if(session.isConnected())
session.write(m.getMessage());
}
}