/*
* BattleField.java
*
* Created on December 19, 2006, 4:05 PM
*
* This file is a part of Shoddy Battle.
* Copyright (C) 2006 Colin Fitzpatrick
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, visit the Free Software Foundation, Inc.
* online at http://gnu.org.
*/
package org.pokenet.server.battle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.pokenet.server.backend.entity.PlayerChar;
import org.pokenet.server.battle.mechanics.BattleMechanics;
import org.pokenet.server.battle.mechanics.ModData;
import org.pokenet.server.battle.mechanics.MoveQueueException;
import org.pokenet.server.battle.mechanics.PokemonType;
import org.pokenet.server.battle.mechanics.ValidationException;
import org.pokenet.server.battle.mechanics.clauses.Clause;
import org.pokenet.server.battle.mechanics.moves.MoveList;
import org.pokenet.server.battle.mechanics.moves.MoveListEntry;
import org.pokenet.server.battle.mechanics.moves.PokemonMove;
import org.pokenet.server.battle.mechanics.moves.MoveList.SpeedSwapEffect;
import org.pokenet.server.battle.mechanics.statuses.StatusEffect;
import org.pokenet.server.battle.mechanics.statuses.field.FieldEffect;
/**
*
* @author Colin
*/
public abstract class BattleField {
/*
* The number of people who can actually participate. This could be four
* later, but for now it will always be two.
*/
protected int m_participants = 2;
/*
* Stores if we are waiting for a switch
*/
protected boolean m_isWaiting = false;
/*
* Store lists of spectators and effects
*/
private ArrayList<PlayerChar> m_spectators = new ArrayList<PlayerChar>();
protected ArrayList<FieldEffect> m_effects = new ArrayList<FieldEffect>();
/*
* The Pokemon in this battlefield
*/
protected Pokemon[][] m_pokemon;
protected int[] m_active = { 0, 0 };
private BattleMechanics m_mechanics;
private boolean m_narration = true;
/*
* Needed for request and wait for switch
* Tells battle threadlets if the player forced to switch, has switched
*/
protected boolean [] m_replace;
/*
* The dispatch thread
*/
protected Thread m_dispatch = null;
// Cache of Struggle.
private static final MoveListEntry m_struggle = MoveList.getDefaultData().getMove("Struggle");
public static MoveListEntry getStruggle() {
return m_struggle;
}
/**
* Forces move executions
*/
public abstract void forceExecuteTurn();
/**
* Adds a spectator to the battle
* @param p
*/
public void addSpectator(PlayerChar p) {
m_spectators.add(p);
}
/**
* Removes a spectator from the battle
* @param p
*/
public void removeSpectator(PlayerChar p) {
m_spectators.remove(p);
}
/**
* Return whether narration is enabled.
*/
public boolean isNarrationEnabled() {
return m_narration;
}
/**
* Set whether to narrate the battle.
*/
public void setNarrationEnabled(boolean enabled) {
m_narration = enabled;
}
/**
* Get the effectiveness of a move against a given pokemon on this field.
*/
public double getEffectiveness(PokemonType move, PokemonType pokemon, boolean enemy) {
return Pokemon.getEffectiveness(m_effects, move, pokemon, enemy);
}
/**
* Detaches the battlefield from all pokemon
*/
private void detachField() {
for (int i = 0; i < m_pokemon.length; ++i) {
detachField(i);
}
}
/**
* Detaches battlefield from a specific party
* @param i
*/
private void detachField(int i) {
Pokemon[] team = m_pokemon[i];
for (int j = 0; j < team.length; ++j) {
if(team[j] != null)
team[j].detachField();
}
}
/**
* Dispose of this object by breaking all links to other objects, making it
* easy to the garbage collector to find and free them.
*/
public void dispose() {
detachField();
m_spectators = null;
m_effects = null;
m_pokemon = null;
m_active = null;
m_mechanics = null;
}
/** Creates a new instance of BattleField */
public BattleField(BattleMechanics mech, Pokemon[][] pokemon) {
m_mechanics = mech;
m_pokemon = pokemon;
/*
* Set m_hasSwitched to 4 to allow for 2 v 2 battles in future
*/
m_replace = new boolean[4];
for(int i = 0; i < m_replace.length; i++)
m_replace[i] = false;
attachField();
setPokemon(pokemon);
}
/**
* Applies weather to the battlefield.
* Must be implemented by children classes.
*/
public abstract void applyWeather();
/**
* Allows children to construct without pokemon.
* @param mech
*/
protected BattleField(BattleMechanics mech) {
m_mechanics = mech;
}
/**
* Get the mechanics used on this battle field.
*/
public BattleMechanics getMechanics() {
return m_mechanics;
}
/**
* Return the instance of Random used on this BattleField.
*/
public Random getRandom() {
return m_mechanics.getRandom();
}
protected void setPokemon(Pokemon[][] pokemon) {
Pokemon[] active = getActivePokemon();
sortBySpeed(active);
for (int i = 0; i < active.length; ++i) {
Pokemon p = active[i];
if(p != null) {
applyEffects(p);
p.switchIn();
}
}
}
/**
* Get a party.
*/
public Pokemon[] getParty(int idx) throws IllegalArgumentException {
if ((idx < 0) || (idx >= m_participants)) {
throw new IllegalArgumentException("0 <= idx < participants");
}
return m_pokemon[idx];
}
/**
* Attach this field to all of its pokemon.
*/
protected void attachField() {
for (int i = 0; i < m_pokemon.length; ++i) {
attachField(i);
}
}
public void attachField(int i) {
Pokemon[] team = m_pokemon[i];
for (int j = 0; j < team.length; ++j) {
if(team[j] != null)
team[j].attachToField(this, i, j);
}
}
/**
* Get the active pokemon.
*/
public Pokemon[] getActivePokemon() {
if ((m_pokemon == null)
|| (m_pokemon[0] == null)
|| (m_pokemon[1] == null))
return null;
return new Pokemon[] {
m_pokemon[0][m_active[0]],
m_pokemon[1][m_active[1]]
};
}
/**
* Apply a new FieldEffect to this BattleField.
* Note that the actual effect passed in is not used -- it is cloned via
* eff.getFieldCopy(), not eff.clone(), the latter of which should return
* the same object.
*/
public boolean applyEffect(FieldEffect eff) {
if(eff != null) {
Iterator<FieldEffect> i = m_effects.iterator();
while (i.hasNext()) {
FieldEffect j = (FieldEffect)i.next();
if (j.isRemovable()) continue;
if (eff.equals(j)) return false;
if (eff.isExclusiveWith(j)) {
// FieldEffects overwrite each other rather than failing if
// another in their class is present.
removeEffect(j);
// We know that no other statuses can possibly be in this
// category, so it is safe to skip the rest of this loop.
break;
}
}
FieldEffect applied = eff.getFieldCopy();
if (!applied.applyToField(this)) return false;
m_effects.add(applied);
// Apply to each pokemon in the field.
Pokemon[] active = getActivePokemon();
if (active != null) {
for (int j = 0; j < active.length; ++j) {
active[j].addStatus(null, applied);
}
}
return true;
}
return false;
}
/**
* Return whether a client's team is valid.
*/
public void validateTeam(Pokemon[] team, int idx) throws ValidationException {
final int length = team.length;
if ((length < 1) || (length > 6)) {
throw new ValidationException("The team is an invalid size.");
}
ModData data = ModData.getDefaultData();
for (int i = 0; i < length; ++i) {
team[i].validate(data);
}
// Check any clauses.
Collections.sort(m_effects, new Comparator<Object>() {
public int compare(Object o1, Object o2) {
StatusEffect e1 = (StatusEffect)o1;
StatusEffect e2 = (StatusEffect)o2;
return e1.getTier() - e2.getTier();
}
});
Iterator<FieldEffect> i = m_effects.iterator();
while (i.hasNext()) {
StatusEffect eff = (StatusEffect)i.next();
if (eff instanceof Clause) {
Clause clause = (Clause)eff;
if (!clause.isTeamValid(this, team, idx)) {
throw new ValidationException("The team violates "
+ clause.getClauseName() + ".");
}
}
}
}
/**
* Synchronise FieldEffects.
*/
public void synchroniseFieldEffects() {
if(m_effects == null)
return;
Iterator<FieldEffect> i = m_effects.iterator();
while (i.hasNext()) {
StatusEffect eff = (StatusEffect)i.next();
if (eff.isRemovable()) {
i.remove();
}
}
}
/**
* Remove a FieldEffect from this field.
*/
public void removeEffect(FieldEffect eff) {
Pokemon[] active = getActivePokemon();
for (int i = 0; i < active.length; ++i) {
eff.unapply(active[i]);
}
eff.unapplyToField(this);
eff.disable();
}
/**
* Returns the first instance of an effect of a certain class that is
* applied to the BattleField.
*/
@SuppressWarnings("unchecked")
public FieldEffect getEffectByType(Class type) {
ArrayList list = getEffectsByType(type);
if (list.size() == 0) {
return null;
}
return (FieldEffect)list.get(0);
}
/**
* Returns a list of the effects of a certain class that are applied to
* this BattleField.
*/
@SuppressWarnings("unchecked")
public ArrayList<FieldEffect> getEffectsByType(Class type) {
ArrayList<FieldEffect> ret = new ArrayList<FieldEffect>();
Iterator<FieldEffect> i = m_effects.iterator();
while (i.hasNext()) {
FieldEffect effect = (FieldEffect)i.next();
if ((effect == null) || (!effect.isActive())) {
continue;
}
if (type.isAssignableFrom(effect.getClass())) {
ret.add(effect);
}
}
return ret;
}
/**
* Obtain a replacement pokemon for the team identified by the parameter.
*/
protected abstract void requestPokemonReplacement(int i);
/**
* Narrate the battle.
*/
public abstract void showMessage(String message);
/**
* Refresh all active pokemon. The exact meaning of this is for the
* implementation to decide.
*/
public abstract void refreshActivePokemon();
/**
* Get the index of a trainer from one of his pokemon.
* @param p a Pokemon who with 100% certainty belongs to one of the clients
*/
public int getPokemonTrainer(Pokemon p) {
Pokemon[] team = m_pokemon[0];
for (int i = 0; i < team.length; ++i) {
if (team[i] == p) {
return 0;
}
}
return 1;
}
/**
* Gets the party index of a pokemon
* @param p
* @return
*/
public int getPokemonPartyIndex(int trainer, Pokemon p) {
/* Check player 1 */
for (int i = 0; i < m_pokemon[trainer].length; i++){
if (m_pokemon[trainer][i] != null && m_pokemon[trainer][i].compareTo(p) == 0) {
return i;
}
}
/* This Pokemon came out of nowhere it seems */
return -1;
}
/**
* Get the name of a trainer by number.
*/
public abstract String getTrainerName(int idx);
/**
* Request moves for the next turn from both players
*/
protected abstract void requestMoves();
/**
* Requests a move from a specific player
* @param trainer
*/
protected abstract void requestMove(int trainer);
/**
* Inform that a pokemon's health was changed.
*/
public abstract void informPokemonHealthChanged(Pokemon poke, int change);
/**
* Inform that a status was applied to a pokemon.
*/
public abstract void informStatusApplied(Pokemon poke, StatusEffect eff);
/**
* Inform that a status effect was removed from a pokemon.
*/
public abstract void informStatusRemoved(Pokemon poke, StatusEffect eff);
/**
* Inform that a pokemon was switched in.
*/
public abstract void informSwitchInPokemon(int trainer, Pokemon poke);
/**
* Inform that a pokemon fainted.
*/
public abstract void informPokemonFainted(int trainer, int idx);
/**
* Inform that a pokemon used a move.
*
* @param poke the pokemon who used the move
* @param name the name of the move that was used
*/
public abstract void informUseMove(Pokemon poke, String name);
/**
* Apply field effects to a pokemon.
*/
private void applyEffects(Pokemon p) {
Iterator<FieldEffect> i = m_effects.iterator();
while (i.hasNext()) {
FieldEffect eff = (FieldEffect)i.next();
if (!eff.isRemovable()) {
p.addStatus(null, eff);
}
}
}
/**
* Switch in a pokemon and apply FieldEffects to it.
*/
public void switchInPokemon(int trainer, int idx) {
m_pokemon[trainer][m_active[trainer]].switchOut();
m_active[trainer] = idx;
Pokemon poke = m_pokemon[trainer][idx];
informSwitchInPokemon(trainer, poke);
applyEffects(poke);
poke.switchIn();
}
/**
* Inform that a player has won.
*/
public abstract void informVictory(int winner);
/**
* Returns the queued battle turns
* @return
*/
public abstract BattleTurn[] getQueuedTurns();
/**
* Queue a move.
*/
public abstract void queueMove(int trainer, BattleTurn move)
throws MoveQueueException;
/**
* Wait for a player to switch pokemon.
*/
public abstract void requestAndWaitForSwitch(int party);
/**
* Get the opponent of the Pokemon passed in.
*/
public Pokemon getOpponent(Pokemon p) {
int idx = getPokemonTrainer(p);
int opponent = (idx == 0) ? 1 : 0;
return m_pokemon[opponent][m_active[opponent]];
}
/**
* Replace a fainted pokemon.
*/
public void replaceFaintedPokemon(int party, int pokemon, boolean search) {
if ((pokemon < 0) || (pokemon > 5)) {
return;
}
switchInPokemon(party, pokemon);
if (!search)
return;
for (int i = 0; i < 2; ++i) {
if (m_pokemon[i][m_active[i]].isFainted()) {
requestPokemonReplacement(i);
return;
}
}
requestMoves();
}
/**
* Execute a turn.
*/
private void executeTurn(BattleTurn turn, int source, int target) {
Pokemon psource = m_pokemon[source][m_active[source]];
if (psource.isFainted()) {
return;
}
if (!turn.isMoveTurn()) {
switchInPokemon(source, turn.getId());
return;
}
psource.executeTurn(turn);
int move = turn.getId();
MoveListEntry entry = psource.getMove(move);
if (entry == null) return;
PokemonMove theMove = entry.getMove();
if (psource.isImmobilised(theMove.getStatusException())) {
return;
}
Pokemon ptarget = m_pokemon[target][m_active[target]];
if (theMove.isAttack() && ptarget.isFainted()) {
informUseMove(psource, entry.getName());
showMessage("But there was no target!");
return;
}
psource.useMove(move, ptarget);
}
/**
* Determine the order in which pokemon attack, etc.
*/
@SuppressWarnings("unchecked")
private void sortBySpeed(Pokemon[] active) {
// Sort pokemon by speed.
ArrayList<Pokemon> list = new ArrayList<Pokemon>(Arrays.asList(active));
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return PokemonWrapper.compareSpeed((Pokemon)o1, (Pokemon)o2);
}
});
}
/**
* Tick status effects at the end of a turn.
*/
@SuppressWarnings("unchecked")
private void tickStatuses(Pokemon[] active) {
sortBySpeed(active);
for (int i = 0; i < active.length; ++i) {
active[i].beginStatusTicks();
}
// For each tier.
final int tiers = StatusEffect.getTierCount();
for (int i = 0; i < tiers; ++i) {
// For each pokemon.
for (int j = 0; j < active.length; ++j) {
Pokemon poke = active[j];
if (poke.isFainted()) continue;
List v = poke.getStatusesByTier(i);
Iterator k = v.iterator();
while (k.hasNext()) {
((StatusEffect)k.next()).tick(poke);
}
}
}
}
/**
* Return the number of party members in a given party who are alive.
*/
public int getAliveCount(int idx) {
if ((idx < 0) || (idx >= m_participants)) {
throw new IllegalArgumentException("0 <= idx < participants");
}
int alive = 0;
Pokemon[] pokemon = m_pokemon[idx];
for (int i = 0; i < pokemon.length; ++i) {
if (pokemon[i] != null && !pokemon[i].isFainted()) {
alive++;
}
}
return alive;
}
/**
* Check if one party has won the battle and inform victory if so.
*/
public void checkBattleEnd(int i) {
if (getAliveCount(i) != 0)
return;
int opponent = ((i == 0) ? 1 : 0);
if (getAliveCount(opponent) == 0) {
// It's a draw!
opponent = -1;
}
informVictory(opponent);
}
/**
* A wrapper for a pokemon and a turn. Can be compared on the basis of
* move priority, or, failing that, speed.
*/
@SuppressWarnings("unchecked")
protected static class PokemonWrapper implements Comparable {
private Pokemon m_poke;
private BattleTurn m_turn;
private int m_idx;
/**
* Initialise a PokemonWrapper with a Pokemon and a BattleTurn.
*/
private PokemonWrapper(Pokemon p, BattleTurn turn, int idx) {
m_poke = p;
m_turn = turn;
m_idx = idx;
}
/**
* Compare based on speed.
*/
public static int compareSpeed(Pokemon p1, Pokemon p2) {
if(p1 == null)
return 1;
if(p2 == null)
return -1;
final int s1 = p1.getStat(Pokemon.S_SPEED);
final int s2 = p2.getStat(Pokemon.S_SPEED);
int comp = 0;
if (s1 > s2) comp = -1;
else if (s2 > s1) comp = 1;
// Note: shoddy.
if (comp != 0) {
if (p1.getField().getEffectByType(SpeedSwapEffect.class) != null) {
return -comp;
}
return comp;
}
// Since the speeds are equal, pick a random pokemon.
return (p1.getField().getRandom().nextBoolean() ? -1 : 1);
}
/**
* Compare this object to another PokemonWrapper.
*/
public int compareTo(Object obj) {
PokemonWrapper comp = (PokemonWrapper)obj;
if ((comp == null) || (comp.m_turn == null))
return -1;
if (m_turn == null)
return 1;
if (m_turn.isMoveTurn() && comp.m_turn.isMoveTurn()) {
int p1 = 0, p2 = 0;
PokemonMove m1 = m_turn.getMove(m_poke);
if (m1 != null) {
p1 = m1.getPriority();
}
PokemonMove m2 = comp.m_turn.getMove(comp.m_poke);
if (m2 != null) {
p2 = m2.getPriority();
}
if (p1 > p2) return -1;
if (p2 > p1) return 1;
return compareSpeed(m_poke, comp.m_poke);
}
return (!m_turn.isMoveTurn() ? -1 : 1);
}
/**
* Sort pokemon and moves in descending order. Reorders the elements of
* the arrays passed in and also returns an array of the indices of the
* pokemon as rearranged.
*/
public static int[] sortMoves(Pokemon[] active, BattleTurn[] move) {
final PokemonWrapper[] wrap = new PokemonWrapper[active.length];
for (int i = 0; i < wrap.length; ++i) {
wrap[i] = new PokemonWrapper(active[i], move[i], i);
}
Collections.sort(Arrays.asList(wrap));
final int[] order = new int[wrap.length];
for (int i = 0; i < wrap.length; ++i) {
PokemonWrapper item = wrap[i];
active[i] = item.m_poke;
move[i] = item.m_turn;
order[i] = item.m_idx;
}
return order;
}
}
/**
* Execute a turn.
*/
public void executeTurn(BattleTurn[] move) {
Pokemon[] active = getActivePokemon();
int[] order = PokemonWrapper.sortMoves(active, move);
for (int i = 0; i < active.length; ++i) {
BattleTurn turn = move[i];
if (turn == null)
continue;
if (turn.isMoveTurn()) {
PokemonMove pokemonMove = turn.getMove(active[i]);
if (pokemonMove != null) {
pokemonMove.beginTurn(move, i, active[i]);
}
}
}
for (int i = 0; i < active.length; ++i) {
int other = (order[i] == 0) ? 1 : 0;
BattleTurn turn = move[i];
if (turn != null) {
executeTurn(turn, order[i], other);
}
}
// Refresh the active array in case a trainer switched.
active = getActivePokemon();
tickStatuses(active);
boolean request = true;
for (int i = 0; i < active.length; ++i) {
// Synchronise statuses.
active[i].synchroniseStatuses();
if (!active[i].isFainted()) {
continue;
}
requestPokemonReplacement(i);
request = false;
}
// Synchronise FieldEffects.
synchroniseFieldEffects();
//showMessage("---");
if (request) {
requestMoves();
}
}
public abstract void clearQueue();
}