package org.pokenet.server.network; import java.sql.ResultSet; import java.util.LinkedList; import java.util.Queue; import org.pokenet.server.GameServer; import org.pokenet.server.backend.entity.Bag; import org.pokenet.server.backend.entity.PlayerChar; import org.pokenet.server.battle.DataService; import org.pokenet.server.battle.Pokemon; import org.pokenet.server.battle.PokemonSpecies; import org.pokenet.server.battle.mechanics.statuses.abilities.IntrinsicAbility; /** * Handles logging players out * @author shadowkanji * */ public class LogoutManager implements Runnable { private Queue<PlayerChar> m_logoutQueue; private Thread m_thread; private boolean m_isRunning; private MySqlManager m_database; /** * Default constructor */ public LogoutManager() { m_database = new MySqlManager(); m_logoutQueue = new LinkedList<PlayerChar>(); m_thread = null; } /** * Returns how many players are in the save queue * @return */ public int getPlayerAmount() { return m_logoutQueue.size(); } /** * Attempts to logout a player by saving their data. Returns true on success * @param player */ private boolean attemptLogout(PlayerChar player) { //Remove player from their map if it hasn't been done already if(player.getMap() != null) player.getMap().removeChar(player); TcpProtocolHandler.removePlayer(player); UdpProtocolHandler.removePlayer(player); GameServer.getInstance().updatePlayerCount(); m_database = new MySqlManager(); if(!m_database.connect(GameServer.getDatabaseHost(), GameServer.getDatabaseUsername(), GameServer.getDatabasePassword())) return false; m_database.selectDatabase(GameServer.getDatabaseName()); //Store all player information if(!savePlayer(player)) { m_database.close(); return false; } //Finally, store that the player is logged out and close connection m_database.query("UPDATE pn_members SET lastLoginServer='null' WHERE id='" + player.getId() + "'"); m_database.close(); GameServer.getServiceManager().getMovementService().removePlayer(player.getName()); return true; } /** * Queues a player to be logged out * @param player */ public void queuePlayer(PlayerChar player) { if(m_thread == null || !m_thread.isAlive()) start(); if(!m_logoutQueue.contains(player)) m_logoutQueue.offer(player); } /** * Called by m_thread.start() */ public void run() { while(m_isRunning) { synchronized(m_logoutQueue) { if(m_logoutQueue.peek() != null) { PlayerChar p = m_logoutQueue.poll(); synchronized(p) { if(p != null) { if(!attemptLogout(p)) { m_logoutQueue.add(p); } else { p.dispose(); System.out.println("INFO: " + p.getName() + " logged out."); p = null; } } } } } try { Thread.sleep(500); } catch (Exception e) {} } m_thread = null; System.out.println("INFO: All player data saved successfully."); } /** * Start this logout manager */ public void start() { if(m_thread == null || !m_thread.isAlive()) { m_thread = new Thread(this); m_isRunning = true; m_thread.start(); } } /** * Stop this logout manager */ public void stop() { //Stop the thread m_isRunning = false; } /** * Saves a player object to the database (Updates an existing player) * @param p * @return */ private boolean savePlayer(PlayerChar p) { try { /* * First, check if they have logged in somewhere else. * This is useful for when as server loses its internet connection */ ResultSet data = m_database.query("SELECT * FROM pn_members WHERE id='" + p.getId() + "'"); data.first(); if(data.getLong("lastLoginTime") == p.getLastLoginTime()) { /* Check they are not trading */ if(p.isTrading()) { /* If the trade is still executing, don't save them yet */ if(!p.getTrade().endTrade()) return false; } /* * Update the player row */ String badges = ""; for(int i = 0; i < 42; i++) { if(p.getBadges()[i] == 1) badges = badges + "1"; else badges = badges + "0"; } m_database.query("UPDATE pn_members SET " + "muted='" + p.isMuted() + "', " + "sprite='" + p.getRawSprite() + "', " + "money='" + p.getMoney() + "', " + "skHerb='" + p.getHerbalismExp() + "', " + "skCraft='" + p.getCraftingExp() + "', " + "skFish='" + p.getFishingExp() + "', " + "skTrain='" + p.getTrainingExp() + "', " + "skCoord='" + p.getCoordinatingExp() + "', " + "skBreed='" + p.getBreedingExp() + "', " + "x='" + p.getX() + "', " + "y='" + p.getY() + "', " + "mapX='" + p.getMapX() + "', " + "mapY='" + p.getMapY() + "', " + "healX='" + p.getHealX() + "', " + "healY='" + p.getHealY() + "', " + "healMapX='" + p.getHealMapX() + "', " + "healMapY='" + p.getHealMapY() + "', " + "isSurfing='" + String.valueOf(p.isSurfing()) + "', " + "badges='" + badges + "' " + "WHERE id='" + p.getId() + "'"); /* * Second, update the party */ //Save all the Pokemon for(int i = 0; i < 6; i++) { if(p.getParty() != null && p.getParty()[i] != null) { if(p.getParty()[i].getDatabaseID() < 1) { //This is a new Pokemon, add it to the database if(saveNewPokemon(p.getParty()[i], p.getName(), m_database) < 1) return false; } else { //Old Pokemon, just update if(!savePokemon(p.getParty()[i], p.getName())) return false; } } } //Save all the Pokemon id's in the player's party if(p.getParty() != null) { m_database.query("UPDATE pn_party SET " + "pokemon0='" + (p.getParty()[0] != null ? p.getParty()[0].getDatabaseID() : -1) + "', " + "pokemon1='" + (p.getParty()[1] != null ? p.getParty()[1].getDatabaseID() : -1) + "', " + "pokemon2='" + (p.getParty()[2] != null ? p.getParty()[2].getDatabaseID() : -1) + "', " + "pokemon3='" + (p.getParty()[3] != null ? p.getParty()[3].getDatabaseID() : -1) + "', " + "pokemon4='" + (p.getParty()[4] != null ? p.getParty()[4].getDatabaseID() : -1) + "', " + "pokemon5='" + (p.getParty()[5] != null ? p.getParty()[5].getDatabaseID() : -1) + "' " + "WHERE member='" + p.getId() + "'"); } else return true; /* * Save the player's bag */ if(p.getBag() == null || !saveBag(p.getBag())) return false; /* * Finally, update all the boxes */ if(p.getBoxes() != null) { for(int i = 0; i < 9; i++) { if(p.getBoxes()[i] != null) { /* Save all pokemon in box */ for(int j = 0; j < p.getBoxes()[i].getPokemon().length; j++) { if(p.getBoxes()[i].getPokemon()[j] != null) { if(p.getBoxes()[i].getPokemon()[j].getDatabaseID() < 1) { /* This is a new Pokemon, create it in the database */ if(saveNewPokemon(p.getBoxes()[i].getPokemon(j), p.getName(), m_database) < 1) return false; } else { /* Update an existing pokemon */ if(!savePokemon(p.getBoxes()[i].getPokemon()[j], p.getName())) { return false; } } } } } } } //Dispose of the player object if(p.getMap() != null) p.getMap().removeChar(p); return true; } else return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Saves a pokemon to the database that didn't exist in it before * @param p */ private int saveNewPokemon(Pokemon p, String currentTrainer, MySqlManager db) { try { /* * Due to issues with Pokemon not receiving abilities, * we're going to ensure they have one */ if(p.getAbility() == null || p.getAbility().getName().equalsIgnoreCase("")) { String [] abilities = PokemonSpecies.getDefaultData().getPossibleAbilities(p.getSpeciesName()); /* First select an ability randomly */ String ab = ""; if(abilities.length == 1) ab = abilities[0]; else ab = abilities[DataService.getBattleMechanics().getRandom().nextInt(abilities.length)]; p.setAbility(IntrinsicAbility.getInstance(ab), true); } /* * Insert the Pokemon into the database */ db.query("INSERT INTO pn_pokemon" + "(name, speciesName, exp, baseExp, expType, isFainted, level, happiness, " + "gender, nature, abilityName, itemName, isShiny, currentTrainerName, originalTrainerName, date, contestStats)" + "VALUES (" + "'" + MySqlManager.parseSQL(p.getName()) +"', " + "'" + MySqlManager.parseSQL(p.getSpeciesName()) +"', " + "'" + String.valueOf(p.getExp()) +"', " + "'" + p.getBaseExp() +"', " + "'" + MySqlManager.parseSQL(p.getExpType().name()) +"', " + "'" + String.valueOf(p.isFainted()) +"', " + "'" + p.getLevel() +"', " + "'" + p.getHappiness() +"', " + "'" + p.getGender() +"', " + "'" + MySqlManager.parseSQL(p.getNature().getName()) +"', " + "'" + MySqlManager.parseSQL(p.getAbilityName()) +"', " + "'" + MySqlManager.parseSQL(p.getItemName()) +"', " + "'" + String.valueOf(p.isShiny()) +"', " + "'" + currentTrainer + "', " + "'" + MySqlManager.parseSQL(p.getOriginalTrainer()) + "', " + "'" + MySqlManager.parseSQL(p.getDateCaught()) + "', " + "'" + p.getContestStatsAsString() + "')"); /* * Get the pokemon's database id and attach it to the pokemon. * This needs to be done so it can be attached to the player in the database later. */ ResultSet result = db.query("SELECT * FROM pn_pokemon WHERE originalTrainerName='" + MySqlManager.parseSQL(p.getOriginalTrainer()) + "' AND date='" + MySqlManager.parseSQL(p.getDateCaught()) + "' AND name='" + p.getSpeciesName() + "' AND exp='" + String.valueOf(p.getExp()) + "'"); result.first(); p.setDatabaseID(result.getInt("id")); db.query("UPDATE pn_pokemon SET move0='" + MySqlManager.parseSQL(p.getMove(0).getName()) + "', move1='" + (p.getMove(1) == null ? "null" : MySqlManager.parseSQL(p.getMove(1).getName())) + "', move2='" + (p.getMove(2) == null ? "null" : MySqlManager.parseSQL(p.getMove(2).getName())) + "', move3='" + (p.getMove(3) == null ? "null" : MySqlManager.parseSQL(p.getMove(3).getName())) + "', hp='" + p.getHealth() + "', atk='" + p.getStat(1) + "', def='" + p.getStat(2) + "', speed='" + p.getStat(3) + "', spATK='" + p.getStat(4) + "', spDEF='" + p.getStat(5) + "', evHP='" + p.getEv(0) + "', evATK='" + p.getEv(1) + "', evDEF='" + p.getEv(2) + "', evSPD='" + p.getEv(3) + "', evSPATK='" + p.getEv(4) + "', evSPDEF='" + p.getEv(5) + "' WHERE id='" + p.getDatabaseID() + "'"); db.query("UPDATE pn_pokemon SET ivHP='" + p.getIv(0) + "', ivATK='" + p.getIv(1) + "', ivDEF='" + p.getIv(2) + "', ivSPD='" + p.getIv(3) + "', ivSPATK='" + p.getIv(4) + "', ivSPDEF='" + p.getIv(5) + "', pp0='" + p.getPp(0) + "', pp1='" + p.getPp(1) + "', pp2='" + p.getPp(2) + "', pp3='" + p.getPp(3) + "', maxpp0='" + p.getMaxPp(0) + "', maxpp1='" + p.getMaxPp(1) + "', maxpp2='" + p.getMaxPp(2) + "', maxpp3='" + p.getMaxPp(3) + "', ppUp0='" + p.getPpUpCount(0) + "', ppUp1='" + p.getPpUpCount(1) + "', ppUp2='" + p.getPpUpCount(2) + "', ppUp3='" + p.getPpUpCount(3) + "' WHERE id='" + p.getDatabaseID() + "'"); return result.getInt("id"); } catch (Exception e) { e.printStackTrace(); return -1; } } /** * Updates a pokemon in the database * @param p */ private boolean savePokemon(Pokemon p, String currentTrainer) { try { /* * Update the pokemon in the database */ m_database.query("UPDATE pn_pokemon SET " + "name='" + MySqlManager.parseSQL(p.getName()) +"', " + "speciesName='" + MySqlManager.parseSQL(p.getSpeciesName()) +"', " + "exp='" + String.valueOf(p.getExp()) +"', " + "baseExp='" + p.getBaseExp() +"', " + "expType='" + MySqlManager.parseSQL(p.getExpType().name()) +"', " + "isFainted='" + String.valueOf(p.isFainted()) +"', " + "level='" + p.getLevel() +"', " + "happiness='" + p.getHappiness() +"', " + "itemName='" + MySqlManager.parseSQL(p.getItemName()) +"', " + "currentTrainerName='" + currentTrainer +"', " + "contestStats='" + p.getContestStatsAsString() +"' " + "WHERE id='" + p.getDatabaseID() + "'"); try { m_database.query("UPDATE pn_pokemon SET move0='" + (p.getMove(0) == null ? "null" : MySqlManager.parseSQL(p.getMove(0).getName())) + "', move1='" + (p.getMove(1) == null ? "null" : MySqlManager.parseSQL(p.getMove(1).getName())) + "', move2='" + (p.getMove(2) == null ? "null" : MySqlManager.parseSQL(p.getMove(2).getName())) + "', move3='" + (p.getMove(3) == null ? "null" : MySqlManager.parseSQL(p.getMove(3).getName())) + "', hp='" + p.getHealth() + "', atk='" + p.getStat(1) + "', def='" + p.getStat(2) + "', speed='" + p.getStat(3) + "', spATK='" + p.getStat(4) + "', spDEF='" + p.getStat(5) + "', evHP='" + p.getEv(0) + "', evATK='" + p.getEv(1) + "', evDEF='" + p.getEv(2) + "', evSPD='" + p.getEv(3) + "', evSPATK='" + p.getEv(4) + "', evSPDEF='" + p.getEv(5) + "' WHERE id='" + p.getDatabaseID() + "'"); } catch (NullPointerException e) { e.printStackTrace(); System.out.println("Database is " + m_database); System.out.println("Pokemon object is " + p); System.out.println("Database ID is " + p.getDatabaseID()); System.out.println("Pokemon name is " + p.getName()); System.out.println("Pokemon moves are " + p.getMove(0).getName() + "|" + p.getMove(1).getName() + "|" + p.getMove(2).getName() + "|" + p.getMove(3).getName()); System.out.println("', hp='" + p.getHealth() + "', atk='" + p.getStat(1) + "', def='" + p.getStat(2) + "', speed='" + p.getStat(3) + "', spATK='" + p.getStat(4) + "', spDEF='" + p.getStat(5) + "', evHP='" + p.getEv(0) + "', evATK='" + p.getEv(1) + "', evDEF='" + p.getEv(2) + "', evSPD='" + p.getEv(3) + "', evSPATK='" + p.getEv(4) + "', evSPDEF='" + p.getEv(5)); } m_database.query("UPDATE pn_pokemon SET ivHP='" + p.getIv(0) + "', ivATK='" + p.getIv(1) + "', ivDEF='" + p.getIv(2) + "', ivSPD='" + p.getIv(3) + "', ivSPATK='" + p.getIv(4) + "', ivSPDEF='" + p.getIv(5) + "', pp0='" + p.getPp(0) + "', pp1='" + p.getPp(1) + "', pp2='" + p.getPp(2) + "', pp3='" + p.getPp(3) + "', maxpp0='" + p.getMaxPp(0) + "', maxpp1='" + p.getMaxPp(1) + "', maxpp2='" + p.getMaxPp(2) + "', maxpp3='" + p.getMaxPp(3) + "', ppUp0='" + p.getPpUpCount(0) + "', ppUp1='" + p.getPpUpCount(1) + "', ppUp2='" + p.getPpUpCount(2) + "', ppUp3='" + p.getPpUpCount(3) + "' WHERE id='" + p.getDatabaseID() + "'"); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Saves a bag to the database. * @param b * @return */ private boolean saveBag(Bag b) { try { //Destroy item data to prevent dupes. m_database.query("DELETE FROM pn_bag WHERE member='" + b.getMemberId() + "'"); for(int i = 0; i < b.getItems().size(); i++) { if(b.getItems().get(i) != null) { /* * NOTE: Items are stored as values 1 - 999 */ m_database.query("INSERT INTO pn_bag (member,item,quantity) VALUES ('" + b.getMemberId()+"', '" + b.getItems().get(i).getItemNumber()+"', '"+ b.getItems().get(i).getQuantity()+"')"); } } return true; } catch (Exception e) { e.printStackTrace(); return false; } } }