package org.pokenet.server.battle.impl; import java.util.LinkedHashSet; import java.util.Set; import org.pokenet.server.backend.ItemProcessor.PokeBall; import org.pokenet.server.backend.entity.PlayerChar; import org.pokenet.server.battle.BattleField; import org.pokenet.server.battle.BattleTurn; import org.pokenet.server.battle.DataService; import org.pokenet.server.battle.Pokemon; import org.pokenet.server.battle.PokemonEvolution; import org.pokenet.server.battle.PokemonSpecies; import org.pokenet.server.battle.PokemonEvolution.EvolutionTypes; import org.pokenet.server.battle.mechanics.BattleMechanics; import org.pokenet.server.battle.mechanics.MoveQueueException; import org.pokenet.server.battle.mechanics.statuses.StatusEffect; import org.pokenet.server.battle.mechanics.statuses.field.FieldEffect; import org.pokenet.server.battle.mechanics.statuses.field.HailEffect; import org.pokenet.server.battle.mechanics.statuses.field.RainEffect; import org.pokenet.server.battle.mechanics.statuses.field.SandstormEffect; import org.pokenet.server.feature.TimeService; import org.pokenet.server.network.TcpProtocolHandler; import org.pokenet.server.network.message.battle.BattleEndMessage; import org.pokenet.server.network.message.battle.BattleExpMessage; import org.pokenet.server.network.message.battle.BattleInitMessage; import org.pokenet.server.network.message.battle.BattleLevelChangeMessage; import org.pokenet.server.network.message.battle.BattleMessage; import org.pokenet.server.network.message.battle.BattleMoveMessage; import org.pokenet.server.network.message.battle.BattleMoveRequest; import org.pokenet.server.network.message.battle.BattleRewardMessage; import org.pokenet.server.network.message.battle.EnemyDataMessage; import org.pokenet.server.network.message.battle.FaintMessage; import org.pokenet.server.network.message.battle.HealthChangeMessage; import org.pokenet.server.network.message.battle.NoPPMessage; import org.pokenet.server.network.message.battle.RunMessage; import org.pokenet.server.network.message.battle.StatusChangeMessage; import org.pokenet.server.network.message.battle.SwitchMessage; import org.pokenet.server.network.message.battle.SwitchRequest; import org.pokenet.server.network.message.battle.BattleEndMessage.BattleEnd; import org.pokenet.server.network.message.battle.BattleRewardMessage.BattleRewardType; /** * A battlefield for wild Pokemon battles * @author shadowkanji */ public class WildBattleField extends BattleField { private final PlayerChar m_player; private Pokemon m_wildPoke; private final BattleTurn[] m_turn = new BattleTurn[2]; private int m_runCount; Set<Pokemon> m_participatingPokemon = new LinkedHashSet<Pokemon>(); private boolean m_finished = false; /** * Constructor * * @param m * @param p * @param wild */ public WildBattleField(BattleMechanics m, PlayerChar p, Pokemon wild) { super(m, new Pokemon[][] { p.getParty(), new Pokemon[] { wild } }); /* Send information to client */ p.setBattling(true); p.setBattleId(0); TcpProtocolHandler.writeMessage(p.getTcpSession(), new BattleInitMessage( true, 1)); TcpProtocolHandler.writeMessage(p.getTcpSession(), new EnemyDataMessage(0, wild)); /* Store variables */ m_player = p; m_wildPoke = wild; m_participatingPokemon.add(p.getParty()[0]); /* Call methods */ // applyWeather(); requestMoves(); } /** * Applies weather effect based on world/map weather */ @Override public void applyWeather() { if (m_player.getMap().isWeatherForced()) { switch (m_player.getMap().getWeather()) { case NORMAL: return; case RAIN: this.applyEffect(new RainEffect()); return; case HAIL: this.applyEffect(new HailEffect()); return; case SANDSTORM: this.applyEffect(new SandstormEffect()); return; default: return; } } else { FieldEffect f = TimeService.getWeatherEffect(); if (f != null) { this.applyEffect(f); } } } @Override public BattleTurn[] getQueuedTurns() { return m_turn; } @Override public String getTrainerName(int idx) { if (idx == 0) { return m_player.getName(); } else { return m_wildPoke.getSpeciesName(); } } @Override public void informPokemonFainted(int trainer, int idx) { /* * If the pokemon is the player's make sure it don't get exp */ if (trainer == 0 && m_participatingPokemon.contains(getParty(trainer)[idx])) { m_participatingPokemon.remove(getParty(trainer)[idx]); } if (m_player != null) TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new FaintMessage(getParty(trainer)[idx].getSpeciesName())); } @Override public void informPokemonHealthChanged(Pokemon poke, int change) { if (m_player != null) { if (getActivePokemon()[0] == poke) { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new HealthChangeMessage(0, change)); } else if (getActivePokemon()[1] == poke) { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new HealthChangeMessage(1, change)); } else { int index = getPokemonPartyIndex(0, poke); if (index > -1) { m_player.getTcpSession().write( "Ph" + String.valueOf(index) + poke.getHealth()); } } } } @Override public void informStatusApplied(Pokemon poke, StatusEffect eff) { if (m_finished) return; if (m_player != null) { if (poke != m_wildPoke) TcpProtocolHandler.writeMessage(m_player .getTcpSession(), new StatusChangeMessage(0, poke.getSpeciesName(), eff .getName(), false)); else if (poke == m_wildPoke) TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new StatusChangeMessage(1, poke.getSpeciesName(), eff.getName(), false)); } } @Override public void informStatusRemoved(Pokemon poke, StatusEffect eff) { if (m_finished) return; if (m_player != null) { if (poke != m_wildPoke && !poke.isFainted()) TcpProtocolHandler.writeMessage(m_player .getTcpSession(), new StatusChangeMessage(0, poke.getSpeciesName(), eff .getName(), true)); else if (poke == m_wildPoke && !poke.isFainted()) TcpProtocolHandler .writeMessage(m_player.getTcpSession(), new StatusChangeMessage(1, poke.getSpeciesName(), eff.getName(), true)); } } @Override public void informSwitchInPokemon(int trainer, Pokemon poke) { if (trainer == 0 && m_player != null) { if (!m_participatingPokemon.contains(poke)) m_participatingPokemon.add(poke); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new SwitchMessage(m_player.getName(), poke.getSpeciesName(), trainer, getPokemonPartyIndex(trainer, poke))); } } @Override public void informUseMove(Pokemon poke, String name) { if (m_player != null) TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleMoveMessage(poke.getSpeciesName(), name)); } @SuppressWarnings("deprecation") @Override public void informVictory(int winner) { m_finished = true; if (winner == 0) { calculateExp(); m_player.removeTempStatusEffects(); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.WON)); } else { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.LOST)); m_player.lostBattle(); } m_player.setBattling(false); dispose(); m_wildPoke = null; if (m_dispatch != null) { /* * This very bad programming but shoddy does it and forces us to do it */ Thread t = m_dispatch; m_dispatch = null; t.stop(); } } /** * Queues a battle turn */ @Override public void queueMove(int trainer, BattleTurn move) throws MoveQueueException { /* Checks the move exists */ if (move.isMoveTurn() && move.getId() != -1 && getActivePokemon()[trainer].getMove(move.getId()) == null) { requestMove(trainer); return; } /* Handle forced switches */ if (m_isWaiting && m_replace != null && m_replace[trainer]) { if (!move.isMoveTurn()) { if (getActivePokemon()[trainer].compareTo(this.getParty(trainer)[move .getId()]) != 0) { this.switchInPokemon(trainer, move.getId()); m_replace[trainer] = false; m_isWaiting = false; return; } } requestPokemonReplacement(trainer); return; } /* Ensure they haven't queued a move already */ if (m_turn[trainer] == null) { /* Handle Pokemon being unhappy and ignoring you */ if (trainer == 0 && !getActivePokemon()[0].isFainted()) { if (getActivePokemon()[0].getHappiness() <= 40) { /* Pokemon is unhappy, they'll do what they feel like */ showMessage(getActivePokemon()[0].getSpeciesName() + " is unhappy!"); int moveID = getMechanics().getRandom().nextInt(4); while (getActivePokemon()[0].getMove(moveID) == null) moveID = getMechanics().getRandom().nextInt(4); move = BattleTurn.getMoveTurn(moveID); } else if (getActivePokemon()[0].getHappiness() < 70) { /* Pokemon is partially unhappy, 50% chance they'll listen to you */ if (getMechanics().getRandom().nextInt(2) == 1) { showMessage(getActivePokemon()[0].getSpeciesName() + " is unhappy!"); int moveID = getMechanics().getRandom().nextInt(4); while (getActivePokemon()[0].getMove(moveID) == null) moveID = getMechanics().getRandom().nextInt(4); move = BattleTurn.getMoveTurn(moveID); } } } if (move.getId() == -1) { if (m_dispatch == null && (trainer == 0 && m_turn[1] != null)) { m_dispatch = new Thread(new Runnable() { public void run() { executeTurn(m_turn); m_dispatch = null; } }); m_dispatch.start(); return; } } else { if (this.getActivePokemon()[trainer].isFainted()) { if (!move.isMoveTurn() && this.getParty(trainer)[move.getId()] != null && this.getParty(trainer)[move.getId()].getHealth() > 0) { switchInPokemon(trainer, move.getId()); requestMoves(); if (!m_participatingPokemon.contains(getActivePokemon()[0])) m_participatingPokemon.add(getActivePokemon()[0]); return; } else { if (trainer == 0 && getAliveCount(0) > 0) { if (getAliveCount(0) > 0) { if (m_participatingPokemon.contains(getActivePokemon()[0])) m_participatingPokemon.remove(getActivePokemon()[0]); requestPokemonReplacement(0); return; } else { /* Player lost the battle */ this.informVictory(1); return; } } } } else { if (move.isMoveTurn()) { if (getActivePokemon()[trainer].mustStruggle()) m_turn[trainer] = BattleTurn .getMoveTurn(-1); else { if (this.getActivePokemon()[trainer].getPp(move.getId()) <= 0) { if (trainer == 0) { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new NoPPMessage(this.getActivePokemon()[trainer] .getMoveName(move.getId()))); requestMove(0); } else { requestMove(1); } return; } else { m_turn[trainer] = move; } } } else { if (this.getActivePokemon()[trainer].isActive() && this.getParty(trainer)[move.getId()] != null && this.getParty(trainer)[move.getId()].getHealth() > 0) { m_turn[trainer] = move; } else { if (trainer == 0) { requestMove(0); } return; } } } } } if (trainer == 0 && m_turn[1] == null) { requestMove(1); return; } if (m_dispatch != null) return; if (m_turn[0] != null && m_turn[1] != null) { m_dispatch = new Thread(new Runnable() { public void run() { executeTurn(m_turn); for (int i = 0; i < m_participants; ++i) { m_turn[i] = null; } m_dispatch = null; } }); m_dispatch.start(); } } /** * Refreshes Pokemon on battlefield */ @Override public void refreshActivePokemon() { m_player.getTcpSession().write( "bh0" + this.getActivePokemon()[0].getHealth()); m_player.getTcpSession().write( "bh1" + this.getActivePokemon()[1].getHealth()); } /** * Requests a new Pokemon (called by moves that force poke switches) */ @Override public void requestAndWaitForSwitch(int party) { if (party == 0) { requestPokemonReplacement(party); if (!m_replace[party]) { return; } m_isWaiting = true; do { synchronized (m_dispatch) { try { m_dispatch.wait(1000); } catch (InterruptedException e) { } } } while ((m_replace != null) && m_replace[party]); } } /** * Generates a wild Pokemon move */ protected void getWildPokemonMove() { if (getActivePokemon()[1] == null) return; try { int moveID = getMechanics().getRandom().nextInt(4); while (getActivePokemon()[1].getMove(moveID) == null) { try { Thread.sleep(100); } catch (Exception e) { } moveID = getMechanics().getRandom().nextInt(4); /* * Stop infinite loops when player disconnects */ if (m_player.getTcpSession() == null || m_player.getTcpSession().isClosing() || !m_player.getTcpSession().isConnected()) break; } queueMove(1, BattleTurn.getMoveTurn(moveID)); } catch (MoveQueueException x) { x.printStackTrace(); } } /** * Requests moves */ @Override protected void requestMoves() { clearQueue(); if (this.getActivePokemon()[0].isActive() && this.getActivePokemon()[1].isActive()) { getWildPokemonMove(); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleMoveRequest()); } } /** * Requests a pokemon replacement */ @Override protected void requestPokemonReplacement(int i) { if (i == 0) { /* * 0 = our player in this case */ TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new SwitchRequest()); } } @Override public void showMessage(String message) { if (m_finished) return; if (m_player != null) TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleMessage(message)); } /** * Returns true if the player can run from the battle * * @return */ private boolean canRun() { // Formula from http://bulbapedia.bulbagarden.net/wiki/Escape float A = getActivePokemon()[0].getStat(Pokemon.S_SPEED); float B = getActivePokemon()[1].getStat(Pokemon.S_SPEED); int C = ++m_runCount; float F = (((A * 32) / (B / 4)) + 30) * C; if (F > 255) return true; if (getMechanics().getRandom().nextInt(255) <= F) return true; return false; } /** * Attempts to run from this battle */ public void run() { if (canRun()) { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new RunMessage( true)); m_player.setBattling(false); this.dispose(); } else { TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new RunMessage( false)); if (m_turn[1] == null) this.getWildPokemonMove(); try { this.queueMove(0, BattleTurn.getMoveTurn(-1)); } catch (MoveQueueException e) { e.printStackTrace(); } } } /** * Throws a Pokeball. Returns true if pokemon was caught * * @param p * @return */ public boolean throwPokeball(PokeBall p) { /* Ensure user doesn't throw a Pokeball while battling */ while (m_dispatch != null) { try { Thread.sleep(500); } catch (Exception e) { } } switch (p) { case POKEBALL: showMessage(m_player.getName() + " threw a Pokeball!"); if (getMechanics().isCaught( m_wildPoke, m_wildPoke.getRareness(), 1.0, 1)) { m_wildPoke.calculateStats(false); m_player.catchPokemon(m_wildPoke); showMessage("You successfuly caught " + m_wildPoke.getSpeciesName()); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.POKEBALL)); m_player.setBattling(false); dispose(); return true; } else showMessage("...but it failed!"); break; case GREATBALL: showMessage(m_player.getName() + " threw a Great Ball!"); if (getMechanics().isCaught( m_wildPoke, m_wildPoke.getRareness(), 1.5, 1)) { m_wildPoke.calculateStats(false); m_player.catchPokemon(m_wildPoke); showMessage("You successfuly caught " + m_wildPoke.getSpeciesName()); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.POKEBALL)); m_player.setBattling(false); dispose(); return true; } else showMessage("...but it failed!"); break; case ULTRABALL: showMessage(m_player.getName() + " threw an Ultra Ball!"); if (getMechanics().isCaught( m_wildPoke, m_wildPoke.getRareness(), 2.0, 1)) { m_wildPoke.calculateStats(false); m_player.catchPokemon(m_wildPoke); showMessage("You successfuly caught " + m_wildPoke.getSpeciesName()); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.POKEBALL)); m_player.setBattling(false); dispose(); return true; } else showMessage("...but it failed!"); break; case MASTERBALL: showMessage(m_player.getName() + " threw a Master Ball!"); if (getMechanics().isCaught( m_wildPoke, m_wildPoke.getRareness(), 255.0, 1)) { m_wildPoke.calculateStats(false); m_player.catchPokemon(m_wildPoke); showMessage("You successfuly caught " + m_wildPoke.getSpeciesName()); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleEndMessage(BattleEnd.POKEBALL)); m_player.setBattling(false); dispose(); return true; } else showMessage("...but it failed!"); break; } return false; } /** * Clears the moves queue */ @Override public void clearQueue() { m_turn[0] = null; m_turn[1] = null; } /** * Requests a move from a specific player */ @Override protected void requestMove(int trainer) { if (trainer == 0) { /* * If its the player, send a move request packet */ TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleMoveRequest()); } else { /* * If its the wild Pokemon, just get the moves */ getWildPokemonMove(); } } /** * Calculates exp gained for Pokemon at the end of battles */ private void calculateExp() { /* * First calculate earnings */ int item = PokemonSpecies.getDefaultData(). getPokemonByName(m_wildPoke.getSpeciesName()).getRandomItem(); if (item > -1) { m_player.getBag().addItem(item, 1); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleRewardMessage(BattleRewardType.ITEM, item)); } else { int money = 5; m_player.setMoney(m_player.getMoney() + money); m_player.updateClientMoney(); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleRewardMessage(BattleRewardType.MONEY, money)); } if (m_participatingPokemon.size() > 0) { double exp = (DataService.getBattleMechanics().calculateExpGain( m_wildPoke, m_participatingPokemon.size())); if (exp == 0) exp = 1; /* * Secondly, calculate EVs and exp */ int[] evs = m_wildPoke.getEffortPoints(); /* * Finally, add the EVs and exp to the participating Pokemon */ for (Pokemon p : m_participatingPokemon) { int index = m_player.getPokemonIndex(p); /* Add the evs */ /* Ensure EVs don't go over limit, before or during addition */ int evTotal = p.getEvTotal(); if (evTotal < 510) { for (int i = 0; i < evs.length; i++) { /* Ensure we don't hit the EV limit */ if (evTotal + evs[i] < 510) { if (p.getEv(i) < 255) { if (p.getEv(i) + evs[i] < 255) { /* Add the EV */ evTotal += evs[i]; p.setEv(i, p.getEv(i) + evs[i]); } else { /* Cap the EV at 255 */ evTotal += (255 - p.getEv(i)); p.setEv(i, 255); } } } else { /* * We're going to hit the EV total limit Only add what's allowed */ evs[i] = 510 - evTotal; if (p.getEv(i) + evs[i] < 255) p.setEv(i, p.getEv(i) + evs[i]); else p.setEv(i, 255); i = evs.length; } } } /* Gain exp/level up and update client */ p.setExp(p.getExp() + exp); //Calculate how much exp is left to next level int expTillLvl = (int)(DataService.getBattleMechanics().getExpForLevel(p, (p.getLevel() + 1)) - p.getExp()); //Make sure that value isn't negative. if (expTillLvl < 0){ expTillLvl = 0; } TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleExpMessage(p.getSpeciesName(), exp, expTillLvl)); String expGain = exp + ""; expGain = expGain.substring(0, expGain.indexOf('.')); m_player.getTcpSession().write("Pe" + index + expGain); double levelExp = DataService.getBattleMechanics().getExpForLevel(p, p.getLevel() + 1) - p.getExp(); if (levelExp <= 0) { PokemonSpecies pokeData = PokemonSpecies.getDefaultData().getPokemonByName( p.getSpeciesName()); boolean evolve = false; /* Handle evolution */ for (int i = 0; i < pokeData.getEvolutions().length; i++) { PokemonEvolution evolution = pokeData.getEvolutions()[i]; if (evolution.getType() == EvolutionTypes.Level) { if (evolution.getLevel() <= p.getLevel() + 1) { p.setEvolution(evolution); m_player.getTcpSession().write("PE" + index); evolve = true; i = pokeData.getEvolutions().length; } } else if (evolution.getType() == EvolutionTypes.HappinessDay) { if (p.getHappiness() > 220 && !TimeService.isNight()) { p.setEvolution(evolution); m_player.getTcpSession().write("PE" + index); evolve = true; i = pokeData.getEvolutions().length; } } else if (evolution.getType() == EvolutionTypes.HappinessNight) { if (p.getHappiness() > 220 && TimeService.isNight()) { p.setEvolution(evolution); m_player.getTcpSession().write("PE" + index); evolve = true; i = pokeData.getEvolutions().length; } } else if (evolution.getType() == EvolutionTypes.Happiness) { if (p.getHappiness() > 220) { p.setEvolution(evolution); m_player.getTcpSession().write("PE" + index); evolve = true; i = pokeData.getEvolutions().length; } } } /* If the Pokemon is evolving, don't move learn just yet */ if (evolve) continue; /* This Pokemon just levelled up! */ p.setHappiness(p.getHappiness() + 2); p.calculateStats(false); int level = DataService.getBattleMechanics().calculateLevel(p); m_player.addTrainingExp(level * 5); int oldLevel = p.getLevel(); String move = ""; /* Move learning */ p.getMovesLearning().clear(); for (int i = oldLevel + 1; i <= level; i++) { if (pokeData.getLevelMoves().get(i) != null) { move = pokeData.getLevelMoves().get(i); p.getMovesLearning().add(move); m_player.getTcpSession().write("Pm" + index + move); } } /* Save the level and update the client */ p.setLevel(level); m_player.getTcpSession().write("Pl" + index + "," + level); TcpProtocolHandler.writeMessage(m_player.getTcpSession(), new BattleLevelChangeMessage(p.getSpeciesName(), level)); m_player.updateClientPokemonStats(index); } } } } @Override public void forceExecuteTurn() { if (m_turn[0] == null) { m_turn[0] = BattleTurn.getMoveTurn(-1); } if (m_turn[1] == null) { m_turn[1] = BattleTurn.getMoveTurn(-1); } executeTurn(m_turn); } }