package org.pokenet.server.battle.impl;
import org.pokenet.server.backend.entity.NonPlayerChar;
import org.pokenet.server.backend.entity.PlayerChar;
import org.pokenet.server.battle.BattleField;
import org.pokenet.server.battle.BattleTurn;
import org.pokenet.server.battle.Pokemon;
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.BattleInitMessage;
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.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 NPC battles
* @author shadowkanji
*
*/
public class NpcBattleField extends BattleField {
private PlayerChar m_player;
private NonPlayerChar m_npc;
private BattleTurn[] m_turn = new BattleTurn[2];
private boolean m_finished = false;
/**
* Constructor
* @param mech
* @param p
* @param n
*/
public NpcBattleField(BattleMechanics mech, PlayerChar p, NonPlayerChar n) {
super(mech, new Pokemon[][] { p.getParty(), n.getParty(p) });
/* Store the player and npc */
m_player = p;
m_npc = n;
/* Start the battle */
TcpProtocolHandler.writeMessage(p.getTcpSession(),
new BattleInitMessage(false, getAliveCount(1)));
/* Send enemy's Pokemon data */
sendPokemonData(p);
/* Set the player's battle id */
m_player.setBattleId(0);
/* Send enemy name */
m_player.getTcpSession().write("bn" + m_npc.getName());
/* Apply weather and request moves */
applyWeather();
requestMoves();
}
/**
* Sends pokemon data to the client
* @param receiver
*/
private void sendPokemonData(PlayerChar receiver) {
for (int i = 0; i < this.getParty(1).length; i++) {
if (this.getParty(1)[i] != null) {
TcpProtocolHandler.writeMessage(receiver.getTcpSession(),
new EnemyDataMessage(i, getParty(1)[i]));
}
}
}
@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 void clearQueue() {
m_turn[0] = null;
m_turn[1] = null;
}
@Override
public BattleTurn[] getQueuedTurns() {
return m_turn;
}
@Override
public String getTrainerName(int idx) {
if(idx == 0)
return m_player.getName();
else
return m_npc.getName();
}
@Override
public void informPokemonFainted(int trainer, int 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());
return;
}
//TODO: Add support for NPC pokemon healing for pokemon in pokeballs
}
}
}
@Override
public void informStatusApplied(Pokemon poke, StatusEffect eff) {
if(m_finished)
return;
if (m_player != null) {
if (getActivePokemon()[0].compareTo(poke) == 0)
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new StatusChangeMessage(0,
poke.getSpeciesName(),
eff.getName(), false));
else if(poke.compareTo(getActivePokemon()[1]) == 0)
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 (getActivePokemon()[0].compareTo(poke) == 0 &&
!getActivePokemon()[0].isFainted())
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new StatusChangeMessage(0,
poke.getSpeciesName(),
eff.getName(), true));
else if(poke.compareTo(getActivePokemon()[1]) == 0 &&
!getActivePokemon()[1].isFainted())
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new StatusChangeMessage(1,
poke.getSpeciesName(),
eff.getName(), true));
}
}
@Override
public void informSwitchInPokemon(int trainer, Pokemon poke) {
if(m_player != null) {
if (trainer == 0) {
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new SwitchMessage(m_player.getName(),
poke.getSpeciesName(),
trainer,
getPokemonPartyIndex(trainer, poke)));
} else {
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new SwitchMessage(m_npc.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));
}
@Override
public void informVictory(int winner) {
m_finished = true;
int money = getParty(1)[0].getLevel() * (getMechanics().getRandom().nextInt(4) + 1);
if (winner == 0) {
/* Reward the player */
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new BattleRewardMessage(BattleRewardType.MONEY,
money));
m_player.setMoney(m_player.getMoney() + money);
/* End the battle */
m_player.removeTempStatusEffects();
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new BattleEndMessage(BattleEnd.WON));
/* Now add Trainer EXP */
int trainerExp = 0;
for(int i = 0; i < getParty(1).length; i++) {
if(getParty(1)[i] != null)
trainerExp += getParty(1)[i].getLevel() / 2;
}
/* If the player got a badge, triple the EXP gained */
if(m_npc.isGymLeader() && !m_player.hasBadge(m_npc.getBadge()))
trainerExp *= 2;
if(trainerExp > 0)
m_player.addTrainingExp(trainerExp);
/* Give the player the badge if it's a gym leader */
if(m_npc.isGymLeader()) {
m_player.addBadge(m_npc.getBadge());
}
} else {
if(m_player.getMoney() - money >= 0) {
m_player.setMoney(m_player.getMoney() - money);
} else {
m_player.setMoney(0);
}
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new BattleEndMessage(BattleEnd.LOST));
m_player.lostBattle();
}
m_player.updateClientMoney();
m_player.setBattling(false);
m_player.setTalking(false);
dispose();
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(); let the thread manually return.*/
}
}
@Override
public void queueMove(int trainer, BattleTurn move)
throws MoveQueueException {
/* Check if 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;
}
/* Queue the move */
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) ||
(trainer == 1 && m_turn[0] != null))) {
m_dispatch = new Thread(new Runnable() {
public void run() {
executeTurn(m_turn);
m_dispatch = null;
}
});
m_dispatch.start();
return;
}
} else {
// Handle a fainted pokemon
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();
return;
} else {
// The player still has pokemon left
if (getAliveCount(trainer) > 0) {
requestPokemonReplacement(trainer);
return;
} else {
// the player has no pokemon left. Announce winner
if (trainer == 0)
this.informVictory(1);
else
this.informVictory(0);
return;
}
}
} else {
// The turn was used to attack!
if (move.isMoveTurn()) {
// Handles Struggle
if (getActivePokemon()[trainer].mustStruggle())
m_turn[trainer] = BattleTurn.getMoveTurn(-1);
else {
// The move has no more PP
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 {
/* Get another move from the npc */
requestMove(1);
}
return;
} else {
// Assign the move to the turn
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 {
requestMove(trainer);
return;
}
}
}
}
}
/* Ensures the npc selected a move */
if(trainer == 0 && m_turn[0] != null && m_turn[1] == null) {
requestMove(1);
return;
}
if (m_dispatch != null)
return;
// Both turns are ready to be performed
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();
}
}
@Override
public void refreshActivePokemon() {
m_player.getTcpSession().write(
"bh0" + this.getActivePokemon()[0].getHealth());
m_player.getTcpSession().write(
"bh1" + this.getActivePokemon()[1].getHealth());
}
@Override
public void requestAndWaitForSwitch(int party) {
requestPokemonReplacement(party);
if (party == 0) {
/* Request a switch from the player */
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]);
}
}
@Override
protected void requestMove(int trainer) {
if(trainer == 0) {
/* Request move from player */
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new BattleMoveRequest());
} else {
/* Request move from npc */
try {
if(getActivePokemon()[1].hasTypeWeakness(getActivePokemon()[0])
&& this.getAliveCount(1) >= 3) {
/* The npc should switch out a different Pokemon */
/* 50:50 chance they will switch */
if(this.getMechanics().getRandom().nextInt(3) == 0) {
int index = 0;
while(this.getParty(1)[index] == null ||
this.getParty(1)[index].isFainted() ||
this.getParty(1)[index].compareTo(getActivePokemon()[1]) == 0) {
try {
Thread.sleep(100);
} catch (Exception e) {}
index = getMechanics().getRandom().nextInt(6);
}
this.queueMove(1, BattleTurn.getSwitchTurn(index));
return;
}
}
/* If they did not switch, select a move */
int moveID = getMechanics().getRandom().nextInt(4);
while (getActivePokemon()[1].getMove(moveID) == null)
moveID = getMechanics().getRandom().nextInt(4);
queueMove(1, BattleTurn.getMoveTurn(moveID));
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void requestMoves() {
clearQueue();
requestMove(1);
requestMove(0);
}
@Override
protected void requestPokemonReplacement(int i) {
if(i == 0) {
/* Request Pokemon replacement from player */
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new SwitchRequest());
} else {
/* Request Pokemon replacement from npc */
if(getAliveCount(1) == 0) {
informVictory(0);
} else {
try {
int index = 0;
while(this.getParty(1)[index] == null ||
this.getParty(1)[index].isFainted()) {
try {
Thread.sleep(100);
} catch (Exception e) {}
index = getMechanics().getRandom().nextInt(6);
}
this.switchInPokemon(1, BattleTurn.getSwitchTurn(index).getId());
requestMoves();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void showMessage(String message) {
if(m_finished)
return;
if(m_player != null)
TcpProtocolHandler.writeMessage(m_player.getTcpSession(),
new BattleMessage(message));
}
@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);
}
}