/*
* ModData.java
*
* Created on May 12, 2007, 5:31 PM
*
* This file is a part of Shoddy Battle.
* Copyright (C) 2007 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* The Free Software Foundation may be visited online at http://www.fsf.org.
*/
package org.pokenet.server.battle.mechanics;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.prefs.Preferences;
import org.pokenet.server.battle.PokemonSpecies;
import org.pokenet.server.battle.PokemonSpeciesData;
import org.pokenet.server.battle.mechanics.moves.MoveList;
import org.pokenet.server.battle.mechanics.moves.MoveSet;
import org.pokenet.server.battle.mechanics.moves.MoveSetData;
import org.pokenet.server.battle.mechanics.statuses.items.HoldItem;
import org.pokenet.server.battle.mechanics.statuses.items.HoldItemData;
/**
* <p>This class encapsulates a complete set of "moddata". Mod data does not
* just include a patch against the pokemon data. In fact, a ModData object
* contains all of the following:
*
* <ul>
* <li>A UUID identifying the server the ModData came from
* <li>A pokemon species database
* <li>A move sets database
* <li>An items database
* <li>A list of moves
* </ul>
*
* @author Colin
*/
public class ModData {
/**
* The mod data file name on the server.
*/
public static final File MOD_DATA_FILE = new File("moddata");
/**
* The registry key holding the storage location on the client.
*/
private static final String REGISTRY_KEY = "storage_location";
/**
* Cache of mod data.
*/
private static final Map<String, ModData> m_map = Collections.synchronizedMap(new HashMap<String, ModData>());
private static final ModData m_default;
/**
* Length of the mod data file.
*/
private long m_dataLength = 0;
/**
* The actual mod data.
*/
private String m_uuid;
private PokemonSpeciesData m_species;
private MoveSetData m_moveSets;
private HoldItemData m_items;
private MoveList m_moves;
static {
m_default = new ModData();
m_default.m_species = PokemonSpecies.getDefaultData();
m_default.m_moveSets = MoveSet.getDefaultData();
m_default.m_items = HoldItem.getDefaultData();
m_default.m_moves = MoveList.getDefaultData();
m_map.put(null, m_default);
}
/**
* Get the name of the ModData.
*/
public String getName() {
return m_uuid;
}
/**
* Return the ModData object corresponding to the given name, attempting
* to load from disc if required.
*/
public static ModData getModData(String name) {
Object o = m_map.get(name);
if (o != null) {
return (ModData)o;
}
File f = new File(getStorageLocation() + name);
if (!f.exists()) {
return null;
}
return new ModData(f);
}
/**
* Get the default ModData.
*/
public static ModData getDefaultData() {
return m_default;
}
/**
* Get the species data.
*/
public PokemonSpeciesData getSpeciesData() {
return m_species;
}
/**
* Get the move set data.
*/
public MoveSetData getMoveSetData() {
return m_moveSets;
}
/**
* Get the hold item data.
*/
public HoldItemData getHoldItemData() {
return m_items;
}
/**
* Get the move data.
*/
public MoveList getMoveData() {
return m_moves;
}
/**
* Save mod data (species, abilities, move sets, and items) to a file
* which can be opened by a client to allow for creating items that
* will play on this mod server.
*/
public void saveModData(OutputStream output) {
try {
m_moveSets.saveToFile(output);
m_items.saveItemData(output);
m_moves.saveMoveList(output);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Set the length of the mod data file.
*/
public void setModDataLength(long length) {
m_dataLength = length;
}
/**
* Get the length of the mod data file.
*/
public long getModDataLength() {
return m_dataLength;
}
/**
* Private constructor.
*/
private ModData() {
}
/**
* Construct a new ModData object via a file.
*/
public ModData(File f) {
loadModData(f);
}
/**
* Load mod data (@see saveModData) from a file.
*/
public void loadModData(File f) {
try {
FileInputStream input = new FileInputStream(f);
m_uuid = f.getName();
m_species = new PokemonSpeciesData();
m_species.loadSpeciesDatabase(input, false);
m_moveSets = new MoveSetData();
m_moveSets.loadFromFile(input);
m_moveSets.pruneMoveSet(); // Slow, but avoids errors.
m_items = new HoldItemData();
m_items.loadItemData(input);
m_moves = new MoveList(false);
m_moves.loadMoveList(input);
input.close();
m_species.cacheMoveSets(m_moves, m_moveSets, false);
m_map.put(m_uuid, this);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Remove a set of items from an array.
*/
private void removeMoves(String[][] haystack, String[] needles) {
for (int i = 0; i < haystack.length; ++i) {
String[] layer = haystack[i];
for (int j = 0; j < layer.length; ++j) {
String strand = layer[j];
for (int k = 0; k < needles.length; ++k) {
if (needles[k].equalsIgnoreCase(strand)) {
layer[j] = null;
break;
}
}
}
}
}
/**
* Handle the moves from a mod data line.
*/
private void handleMoves(int species, ArrayList<String> moves, int category) {
MoveSet set = m_moveSets.getMoveSet(species);
ArrayList<String> removals = new ArrayList<String>();
ArrayList<String> additions = new ArrayList<String>();
Iterator<String> i = moves.iterator();
while (i.hasNext()) {
String item = (String)i.next();
char c = item.charAt(0);
if (c == '-') {
removals.add(item.substring(1).trim());
} else {
String move;
if (c == '+') {
move = item.substring(1);
} else {
move = item;
}
additions.add(move.trim());
}
}
String[][] arr = set.getMoves();
removeMoves(arr, (String[])removals.toArray(new String[removals.size()]));
ArrayList<String> update = new ArrayList<String>(Arrays.asList(arr[category]));
update.addAll(additions);
arr[category] = (String[])update.toArray(new String[update.size()]);
}
/**
* Handle the abilities in a mod data line.
*/
private void handleAbilities(String species, ArrayList<String> abilities) {
String arr[] = m_species.getAbilityNames(species);
if (arr == null) {
System.out.println("Warning: problematic abilities for " + species + ".");
arr = new String[0];
}
ArrayList<String> names =
new ArrayList<String>(Arrays.asList(arr));
Iterator<String> i = abilities.iterator();
while (i.hasNext()) {
String item = (String)i.next();
char c = item.charAt(1);
String name = item.substring(2).trim();
if (c == '+') {
/**if (IntrinsicAbility.getInstance(name) == null) {
System.out.println("Warning: no such ability: " + name);
}**/
names.add(name);
} else {
names.remove(name);
}
}
}
/**
* Handle the items in a mod data line.
*/
private void handleItems(String species, ArrayList<String> items) {
Iterator<String> i = items.iterator();
while (i.hasNext()) {
String item = (String)i.next();
char c = item.charAt(1);
String name = item.substring(2).trim();
if (c == '+') {
m_items.addExclusiveItem(name, species);
} else {
m_items.removeExclusiveItem(name, species);
}
}
}
/**
* Handle a stat modification.
*/
private void modifyStat(int species, String part) {
String[] parts = part.split(" *: *");
String stat = parts[0].toLowerCase();
final String[] stats = { "hp", "atk", "def", "spd", "satk", "sdef" };
int value = Integer.valueOf(parts[1]).intValue();
int i = 0;
for (; i < stats.length; ++i) {
if (stats[i].equalsIgnoreCase(stat)) {
break;
}
}
if (i != stats.length) {
int[] base = m_species.getSpecies(species).getBaseStats();
base[i] = value;
} else {
System.out.println("Could not identify stat: " + stat);
}
}
/**
* Handle an illegal moveset.
*/
private void handleIllegalMoveset(int species, String moveset) {
// To be done (tbd).
}
/**
* Handle one line of a data patch file.
*/
private void parsePatchLine(String line, int category) {
// Collapse white space.
line = line.replaceAll("[ \\n\\r\\t]+", " ");
// Find the colon between the pokemon name and the data.
int idx = line.indexOf(':');
if (idx == -1) {
System.out.println("Malformed patch file statement: " + line);
return;
}
String species = line.substring(0, idx).trim();
int id = m_species.getPokemonByName(species).getSpecies();
if (id == -1) {
System.out.println("Warning: no existing species of " + species + ".");
return;
}
ArrayList<String> moves = new ArrayList<String>();
ArrayList<String> abilities = new ArrayList<String>();
ArrayList<String> items = new ArrayList<String>();
String[] parts = line.substring(idx + 1).trim().split(" *, *");
for (int i = 0; i < parts.length; ++i) {
String part = parts[i];
int length = part.length();
if (length == 0) {
//System.out.println("Warning: empty element: " + line);
continue;
}
if (part.charAt(0) == '~') {
handleIllegalMoveset(id, part.substring(1).trim());
continue;
}
if (part.indexOf(':') != -1) {
modifyStat(id, part.trim());
continue;
}
char s = (length > 1) ? part.charAt(1) : '\0';
boolean sign = (length > 1) ? ((s == '+') || (s == '-')) : false;
switch (part.charAt(0)) {
case 'a':
if (sign) {
// It's an ability.
abilities.add(part);
break;
}
case 'i':
if (sign) {
// It's an item.
items.add(part);
break;
}
default:
// It's a move.
moves.add(part);
break;
}
}
handleMoves(id, moves, category);
handleAbilities(species, abilities);
handleItems(species, items);
}
public void applyPatch(InputStream stream) throws IOException {
applyPatch(stream, 0);
}
/**
* Apply a mod data patch to this ModData object.
*/
public void applyPatch(InputStream stream, int category) throws IOException {
DataInputStream in = new DataInputStream(stream);
while (true) {
StringBuffer buffer = new StringBuffer();
while (true) {
try {
byte c = in.readByte();
if (c == ';') {
break;
}
buffer.append((char)(c & 0xff));
} catch (EOFException e) {
return;
}
}
try {
parsePatchLine(buffer.toString(), category);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Get the location where mod data is stored on the hard disc.
*/
public static String getStorageLocation() {
Preferences prefs = Preferences.userRoot();
return prefs.get(REGISTRY_KEY, null);
}
/**
* Set the location where mod data is stored on the hard disc.
*/
public static void setStorageLocation(String value) {
Preferences prefs = Preferences.userRoot();
value.replace('/', File.separatorChar);
value.replace('\\', File.separatorChar);
if (!value.endsWith(File.separator)) {
value += File.separator;
}
prefs.put(REGISTRY_KEY, value);
}
/**
* Add a server to the map of uuid -> server name.
*/
public static void addServer(String uuid, String name) {
Preferences.userRoot().put("map." + uuid, name);
}
/**
* Find a server name given its uuid.
*/
public static String getServerName(String uuid) {
return Preferences.userRoot().get("map." + uuid, null);
}
}