1819 lines
84 KiB
Java
1819 lines
84 KiB
Java
package prc.autodoc;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
import prc.autodoc.Main.SpellType;
|
|
//import java.util.regex.*;
|
|
|
|
/* Static import in order to let me use the enum constants in switches */
|
|
import static prc.autodoc.Main.SpellType.*;
|
|
|
|
import static prc.Main.*;
|
|
import static prc.autodoc.Main.*;
|
|
|
|
/**
|
|
* This class contains the methods for generation of the raw manual page contents.
|
|
*
|
|
* @author Ornedan
|
|
*/
|
|
public class EntryGeneration {
|
|
/**
|
|
* Handles creation of the skill pages.
|
|
*/
|
|
public static void doSkills() {
|
|
String skillPath = contentPath + "skills" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
path = null,
|
|
icon = null;
|
|
boolean errored;
|
|
|
|
skills = new HashMap<Integer, GenericEntry>();
|
|
Data_2da skills2da = twoDA.get("skills");
|
|
|
|
for (int i = 0; i < skills2da.getEntryCount(); i++) {
|
|
errored = false;
|
|
// Get the name of the skill and check if it's valid
|
|
name = tlk.get(skills2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for skill " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Same for description
|
|
text = htmlizeTLK(tlk.get(skills2da.getEntry("Description", i)));
|
|
// Again, check if we had a valid description
|
|
if (tlk.get(skills2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for skill " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// And icon
|
|
icon = skills2da.getEntry("Icon", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for skill " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Build the final path
|
|
path = skillPath + i + ".html";
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the skill into a hashmap
|
|
skills.put(i, new GenericEntry(name, text, icon, path, i));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for skill " + i + ": " + name);
|
|
}
|
|
// Force a clean-up of dead objects. This will keep discarded objects from slowing down the program as it
|
|
// hits the memory limit
|
|
System.gc();
|
|
}
|
|
|
|
/**
|
|
* Handles creation of the crafting itemprop pages.
|
|
*/
|
|
public static void doCrafting() {
|
|
String craftPath = contentPath + "itemcrafting" + fileSeparator;
|
|
|
|
String name = null,
|
|
text = null,
|
|
path = null,
|
|
icon = null;
|
|
boolean errored;
|
|
|
|
craft_armour = new HashMap<Integer, GenericEntry>();
|
|
craft_weapon = new HashMap<Integer, GenericEntry>();
|
|
craft_ring = new HashMap<Integer, GenericEntry>();
|
|
craft_wondrous = new HashMap<Integer, GenericEntry>();
|
|
Data_2da craft_armour_2da = twoDA.get("craft_armour");
|
|
Data_2da craft_weapon_2da = twoDA.get("craft_weapon");
|
|
Data_2da craft_ring_2da = twoDA.get("craft_ring");
|
|
Data_2da craft_wondrous_2da = twoDA.get("craft_wondrous");
|
|
|
|
icon = "";
|
|
|
|
for (int i = 0; i < craft_armour_2da.getEntryCount(); i++) {
|
|
errored = false;
|
|
// Get the name of the skill and check if it's valid
|
|
name = tlk.get(craft_armour_2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for property " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Same for description
|
|
text = htmlizeTLK(tlk.get(craft_armour_2da.getEntry("Description", i)));
|
|
// Again, check if we had a valid description
|
|
if (tlk.get(craft_armour_2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for property " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the final path
|
|
path = craftPath + "armour" + i + ".html";
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the skill into a hashmap
|
|
craft_armour.put(i, new GenericEntry(name, text, icon, path, i));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for property " + i + ": " + name);
|
|
}
|
|
|
|
for (int i = 0; i < craft_weapon_2da.getEntryCount(); i++) {
|
|
errored = false;
|
|
// Get the name of the skill and check if it's valid
|
|
name = tlk.get(craft_weapon_2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for property " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Same for description
|
|
text = htmlizeTLK(tlk.get(craft_weapon_2da.getEntry("Description", i)));
|
|
// Again, check if we had a valid description
|
|
if (tlk.get(craft_weapon_2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for property " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the final path
|
|
path = craftPath + "weapon" + i + ".html";
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the skill into a hashmap
|
|
craft_weapon.put(i, new GenericEntry(name, text, icon, path, i));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for property " + i + ": " + name);
|
|
}
|
|
|
|
for (int i = 0; i < craft_ring_2da.getEntryCount(); i++) {
|
|
errored = false;
|
|
// Get the name of the skill and check if it's valid
|
|
name = tlk.get(craft_ring_2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for property " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Same for description
|
|
text = htmlizeTLK(tlk.get(craft_ring_2da.getEntry("Description", i)));
|
|
// Again, check if we had a valid description
|
|
if (tlk.get(craft_ring_2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for property " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the final path
|
|
path = craftPath + "ring" + i + ".html";
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the skill into a hashmap
|
|
craft_ring.put(i, new GenericEntry(name, text, icon, path, i));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for property " + i + ": " + name);
|
|
}
|
|
|
|
for (int i = 0; i < craft_wondrous_2da.getEntryCount(); i++) {
|
|
errored = false;
|
|
// Get the name of the skill and check if it's valid
|
|
name = tlk.get(craft_wondrous_2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for property " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Same for description
|
|
text = htmlizeTLK(tlk.get(craft_wondrous_2da.getEntry("Description", i)));
|
|
// Again, check if we had a valid description
|
|
if (tlk.get(craft_wondrous_2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for property " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the final path
|
|
path = craftPath + "wondrous" + i + ".html";
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the skill into a hashmap
|
|
craft_wondrous.put(i, new GenericEntry(name, text, icon, path, i));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for property " + i + ": " + name);
|
|
}
|
|
// Force a clean-up of dead objects. This will keep discarded objects from slowing down the program as it
|
|
// hits the memory limit
|
|
System.gc();
|
|
}
|
|
|
|
/**
|
|
* Prints normal & epic spells and psionic powers.
|
|
* As of now, all of these are similar enough to share the same
|
|
* template, so they can be done here together.
|
|
* <p>
|
|
* The enumeration class used here is found at the end of the file
|
|
*/
|
|
public static void doSpells() {
|
|
String spellPath = contentPath + "spells" + fileSeparator,
|
|
epicPath = contentPath + "epic_spells" + fileSeparator,
|
|
psiPath = contentPath + "psionic_powers" + fileSeparator,
|
|
utterPath = contentPath + "utterances" + fileSeparator,
|
|
invocPath = contentPath + "invocations" + fileSeparator,
|
|
manPath = contentPath + "maneuvers" + fileSeparator;
|
|
|
|
String path = null,
|
|
name = null,
|
|
text = null,
|
|
icon = null,
|
|
subradName = null,
|
|
subradIcon = null;
|
|
List<Tuple<String, String>> subradials = null;
|
|
boolean errored;
|
|
int subRadial;
|
|
|
|
SpellType spelltype = NONE;
|
|
|
|
spells = new HashMap<Integer, SpellEntry>();
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
|
|
for (int i = 0; i < spells2da.getEntryCount(); i++) {
|
|
spelltype = NONE;
|
|
errored = false;
|
|
|
|
if (isNormalSpell(spells2da, i)) spelltype = NORMAL;
|
|
else if (isEpicSpell(spells2da, i)) spelltype = EPIC;
|
|
else if (isPsionicPower(spells2da, i)) spelltype = PSIONIC;
|
|
else if (isTruenameUtterance(spells2da, i)) spelltype = UTTERANCE;
|
|
else if (isInvocation(spells2da, i)) spelltype = INVOCATION;
|
|
else if (isManeuver(spells2da, i)) spelltype = MANEUVER;
|
|
|
|
if (spelltype != NONE) {
|
|
name = tlk.get(spells2da.getEntry("Name", i))
|
|
.replaceAll("/", " / "); // Let the UA insert line breaks if necessary
|
|
if (verbose) System.out.println("Generating entry data for " + name);
|
|
// Check the name for validity
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for spell " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Handle the contents
|
|
text = htmlizeTLK(tlk.get(spells2da.getEntry("SpellDesc", i)));
|
|
// Check the description validity
|
|
if (tlk.get(spells2da.getEntry("SpellDesc", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for spell " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Do the icon
|
|
icon = spells2da.getEntry("IconResRef", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for spell " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Handle subradials, if any
|
|
subradials = null;
|
|
// Assume that if there are any, the first slot is always non-****
|
|
if (!spells2da.getEntry("SubRadSpell1", i).equals("****")) {
|
|
subradials = new ArrayList<Tuple<String, String>>(5);
|
|
for (int j = 1; j <= 5; j++) {
|
|
// Also assume that all the valid entries are in order, such that if Nth SubRadSpell entry
|
|
// is ****, all > N are also ****
|
|
if (spells2da.getEntry("SubRadSpell" + j, i).equals("****"))
|
|
break;
|
|
try {
|
|
subRadial = Integer.parseInt(spells2da.getEntry("SubRadSpell" + j, i));
|
|
|
|
// Try name
|
|
subradName = tlk.get(spells2da.getEntry("Name", subRadial))
|
|
.replaceAll("/", " / ");
|
|
// Check the name for validity
|
|
if (subradName.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid Name entry for spell " + subRadial);
|
|
errored = true;
|
|
}
|
|
|
|
// Try icon
|
|
subradIcon = spells2da.getEntry("IconResRef", subRadial);
|
|
if (subradIcon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for spell " + subRadial + ": " + subradName);
|
|
errored = true;
|
|
}
|
|
|
|
// Build list
|
|
subradials.add(new Tuple<String, String>(subradName, Icons.buildIcon(subradIcon)));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Spell " + i + ": " + name + " contains an invalid SubRadSpell" + j + " entry");
|
|
errored = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the path
|
|
switch (spelltype) {
|
|
case NORMAL:
|
|
path = spellPath + i + ".html";
|
|
break;
|
|
case EPIC:
|
|
path = epicPath + i + ".html";
|
|
break;
|
|
case PSIONIC:
|
|
path = psiPath + i + ".html";
|
|
break;
|
|
case UTTERANCE:
|
|
path = utterPath + i + ".html";
|
|
break;
|
|
case INVOCATION:
|
|
path = invocPath + i + ".html";
|
|
break;
|
|
case MANEUVER:
|
|
path = manPath + i + ".html";
|
|
break;
|
|
|
|
default:
|
|
throw new AssertionError("Unhandled spelltype: " + spelltype);
|
|
}
|
|
|
|
// Check if we had any errors. If we did, and the error tolerance flag isn't up, skip generating this entry
|
|
if (!errored || tolErr) {
|
|
// Store a data structure represeting the entry into a hashmap
|
|
spells.put(i, new SpellEntry(name, text, icon, path, i, spelltype, subradials));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for spell " + i + ": " + name);
|
|
}
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain a psionic power's class-specific
|
|
* entry.
|
|
*/
|
|
public static void listPsionicPowers(TwoDAStore twoDA) {
|
|
// A map of power name to class-specific spells.2da entry
|
|
psiPowMap = new HashMap<String, Integer>();
|
|
|
|
HashMap<String, Data_2da> potentials = twoDA.findAll("cls_psipw_");
|
|
|
|
listAMSEntries(potentials, psiPowMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain a truenaming utterance's
|
|
*/
|
|
public static void listTruenameUtterances(TwoDAStore twoDA) {
|
|
// A map of power name to class-specific spells.2da entry
|
|
utterMap = new HashMap<String, Integer>();
|
|
|
|
// Load cls_*_utter.2da
|
|
HashMap<String, Data_2da> potentials = twoDA.findAll("_utter");
|
|
|
|
listAMSEntries(potentials, utterMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain invocations
|
|
*/
|
|
public static void listInvocations(TwoDAStore twoDA) {
|
|
// A map of power name to class-specific spells.2da entry
|
|
invMap = new HashMap<String, Integer>();
|
|
|
|
var potentials = twoDA.findAll("cls_inv_");
|
|
|
|
listAMSEntries(potentials, invMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain maneuvers
|
|
*/
|
|
public static void listManeuvers(TwoDAStore twoDA) {
|
|
// A map of power name to class-specific spells.2da entry
|
|
maneuverMap = new HashMap<String, Integer>();
|
|
|
|
var potentials = twoDA.findAll("cls_move_");
|
|
|
|
listAMSEntries(potentials, maneuverMap);
|
|
}
|
|
|
|
/**
|
|
* Does the actual list generation for listPsionicPowers() and
|
|
* listTruenameUtterances().
|
|
*
|
|
* @param fileNames List of 2da files that contain the entries to be listed
|
|
* @param storeMap Map to store the entries in
|
|
*/
|
|
private static void listAMSEntries(HashMap<String, Data_2da> list2das, Map<String, Integer> storeMap) {
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
// Parse the 2das
|
|
list2das.entrySet().stream().forEach(
|
|
list2da -> {
|
|
var value2da = list2da.getValue();
|
|
for (int i = 0; i < value2da.getEntryCount(); i++) {
|
|
// Column FeatID is used to determine if the row specifies the main entry of a power
|
|
if (!value2da.getEntry("FeatID", i).equals("****")) {
|
|
try {
|
|
//look up spells.2da name of the realspellid if we don't have a name column
|
|
if (value2da.getEntry("Name", i) == null) {
|
|
storeMap.put(tlk.get(spells2da.getEntry("Name", value2da.getEntry("RealSpellID", i))), Integer.parseInt(value2da.getEntry("RealSpellID", i)));
|
|
} else {
|
|
storeMap.put(tlk.get(value2da.getEntry("Name", i)), Integer.parseInt(value2da.getEntry("SpellID", i)));
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid SpellID entry in " + value2da.getName() + ", line " + i);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A small convenience method for wrapping all the normal spell checks into
|
|
* one.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if any of the class spell level columns contain a number,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isNormalSpell(Data_2da spells2da, int entryNum) {
|
|
if (!spells2da.getEntry("Bard", entryNum).equals("****")) return true;
|
|
if (!spells2da.getEntry("Cleric", entryNum).equals("****")) return true;
|
|
if (!spells2da.getEntry("Druid", entryNum).equals("****")) return true;
|
|
if (!spells2da.getEntry("Paladin", entryNum).equals("****")) return true;
|
|
if (!spells2da.getEntry("Ranger", entryNum).equals("****")) return true;
|
|
if (!spells2da.getEntry("Wiz_Sorc", entryNum).equals("****")) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* A small convenience method for testing if the given entry contains a
|
|
* epic spell.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if the impactscript name starts with strings specified in settings,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isEpicSpell(Data_2da spells2da, int entryNum) {
|
|
for (String check : settings.epicspellSignatures)
|
|
if (spells2da.getEntry("ImpactScript", entryNum).startsWith(check)) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A small convenience method for testing if the given entry contains a
|
|
* psionic power. This is determined by whether the power's id is
|
|
* in the psiPowMap Map.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if entryNum in spells2da contains a psionic power,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isPsionicPower(Data_2da spells2da, int entryNum) {
|
|
return psiPowMap.containsValue(entryNum);
|
|
}
|
|
|
|
/**
|
|
* A small convenience method for testing if the given entry contains a
|
|
* truenaming utterance. This is determined by whether the power's id is
|
|
* in the utterMap Map.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if entryNum in spells2da contains an utterance,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isTruenameUtterance(Data_2da spells2da, int entryNum) {
|
|
return utterMap.containsValue(entryNum);
|
|
}
|
|
|
|
/**
|
|
* A small convenience method for testing if the given entry contains an
|
|
* invocation. This is determined by whether the power's id is
|
|
* in the utterMap Map.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if entryNum in spells2da contains an utterance,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isInvocation(Data_2da spells2da, int entryNum) {
|
|
return invMap.containsValue(entryNum);
|
|
}
|
|
|
|
/**
|
|
* A small convenience method for testing if the given entry contains a
|
|
* maneuver. This is determined by whether the power's id is
|
|
* in the utterMap Map.
|
|
*
|
|
* @param spells2da the Data_2da entry containing spells.2da
|
|
* @param entryNum the line number to use for testing
|
|
* @return <code>true</code> if entryNum in spells2da contains an utterance,
|
|
* <code>false</code> otherwise
|
|
*/
|
|
private static boolean isManeuver(Data_2da spells2da, int entryNum) {
|
|
return maneuverMap.containsValue(entryNum);
|
|
}
|
|
|
|
|
|
/**
|
|
* Build the preliminary list of master feats, without the child feats
|
|
* linked in.
|
|
*/
|
|
public static void preliMasterFeats() {
|
|
String mFeatPath = contentPath + "master_feats" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
path = null;
|
|
FeatEntry entry = null;
|
|
boolean errored;
|
|
|
|
masterFeats = new HashMap<Integer, FeatEntry>();
|
|
Data_2da masterFeats2da = twoDA.get("masterfeats");
|
|
|
|
for (int i = 0; i < masterFeats2da.getEntryCount(); i++) {
|
|
// Skip blank rows
|
|
if (masterFeats2da.getEntry("LABEL", i).equals("****")) continue;
|
|
errored = false;
|
|
|
|
// Get the name and validate it
|
|
name = tlk.get(masterFeats2da.getEntry("STRREF", i));
|
|
if (verbose) System.out.println("Generating preliminary data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for masterfeat " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the entry data
|
|
text = htmlizeTLK(tlk.get(masterFeats2da.getEntry("DESCRIPTION", i)));
|
|
// Check the description validity
|
|
if (tlk.get(masterFeats2da.getEntry("DESCRIPTION", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for masterfeat " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Add in the icon
|
|
String icon = masterFeats2da.getEntry("ICON", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for masterfeat " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Build the path
|
|
path = mFeatPath + i + ".html";
|
|
|
|
if (!errored || tolErr) {
|
|
// Store the entry to wait for further processing
|
|
// Masterfeats start as class feats and are converted into general feats if any child
|
|
// is a general feat
|
|
entry = new FeatEntry(name, text, icon, path, i, false, true, null);
|
|
masterFeats.put(i, entry);
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry data for masterfeat " + i + ": " + name);
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
|
|
/**
|
|
* Build the preliminary list of feats, without master / successor / predecessor feats
|
|
* linked in.
|
|
*/
|
|
public static void preliFeats() {
|
|
String featPath = contentPath + "feats" + fileSeparator,
|
|
epicPath = contentPath + "epic_feats" + fileSeparator,
|
|
classFeatPath = contentPath + "class_feats" + fileSeparator,
|
|
classEpicPath = contentPath + "class_epic_feats" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
icon = null,
|
|
path = null,
|
|
subradName = null,
|
|
subradIcon = null;
|
|
FeatEntry entry = null;
|
|
List<Tuple<String, String>> subradials = null;
|
|
boolean isEpic = false,
|
|
isClassFeat = false;
|
|
boolean errored;
|
|
int featSpell,
|
|
subRadial;
|
|
|
|
feats = new HashMap<Integer, FeatEntry>();
|
|
Data_2da feats2da = twoDA.get("feat");
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
|
|
for (int i = 0; i < feats2da.getEntryCount(); i++) {
|
|
// Skip blank rows and markers
|
|
if (feats2da.getEntry("LABEL", i).equals("****") ||
|
|
feats2da.getEntry("FEAT", i).equals("****"))
|
|
continue;
|
|
// Skip the ISC & Epic spell markers
|
|
if (feats2da.getEntry("LABEL", i).equals("ReservedForISCAndESS")) continue;
|
|
errored = false;
|
|
|
|
// Get the name and validate it
|
|
name = tlk.get(feats2da.getEntry("FEAT", i));
|
|
if (verbose) System.out.println("Generating preliminary data for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for feat " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the entry data
|
|
text = htmlizeTLK(tlk.get(feats2da.getEntry("DESCRIPTION", i)));
|
|
// Check the description validity
|
|
if (tlk.get(feats2da.getEntry("DESCRIPTION", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for feat " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
// Add in the icon
|
|
icon = feats2da.getEntry("ICON", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for feat " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Handle subradials, if any
|
|
subradials = null;
|
|
if (!feats2da.getEntry("SPELLID", i).equals("****")) {
|
|
try {
|
|
featSpell = Integer.parseInt(feats2da.getEntry("SPELLID", i));
|
|
// Assume that if there are any, the first slot is always non-****
|
|
if (!spells2da.getEntry("SubRadSpell1", featSpell).equals("****")) {
|
|
subradials = new ArrayList<Tuple<String, String>>(5);
|
|
for (int j = 1; j <= 5; j++) {
|
|
// Also assume that all the valid entries are in order, such that if Nth SubRadSpell entry
|
|
// is ****, all > N are also ****
|
|
if (spells2da.getEntry("SubRadSpell" + j, featSpell).equals("****"))
|
|
break;
|
|
try {
|
|
subRadial = Integer.parseInt(spells2da.getEntry("SubRadSpell" + j, featSpell));
|
|
|
|
// Try name
|
|
subradName = tlk.get(spells2da.getEntry("Name", subRadial))
|
|
.replaceAll("/", " / ");
|
|
// Check the name for validity
|
|
if (subradName.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid Name entry for spell " + subRadial);
|
|
errored = true;
|
|
}
|
|
|
|
// Try icon
|
|
subradIcon = spells2da.getEntry("IconResRef", subRadial);
|
|
if (subradIcon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for spell " + subRadial + ": " + subradName);
|
|
errored = true;
|
|
}
|
|
|
|
// Build list
|
|
subradials.add(new Tuple<String, String>(subradName, Icons.buildIcon(subradIcon)));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Spell " + featSpell + ": " + name + " contains an invalid SubRadSpell" + j + " entry");
|
|
errored = true;
|
|
}
|
|
}
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid SPELLID for feat " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
}
|
|
|
|
// Classification
|
|
isEpic = feats2da.getEntry("PreReqEpic", i).equals("1");
|
|
isClassFeat = !feats2da.getEntry("ALLCLASSESCANUSE", i).equals("1");
|
|
|
|
// Get the path
|
|
if (isEpic) {
|
|
if (isClassFeat) path = classEpicPath + i + ".html";
|
|
else path = epicPath + i + ".html";
|
|
} else {
|
|
if (isClassFeat) path = classFeatPath + i + ".html";
|
|
else path = featPath + i + ".html";
|
|
}
|
|
|
|
if (!errored || tolErr) {
|
|
// Create the entry data structure
|
|
entry = new FeatEntry(name, text, icon, path, i, isEpic, isClassFeat, subradials);
|
|
// Store the entry to wait for further processing
|
|
feats.put(i, entry);
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry data for feat " + i + ": " + name);
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
|
|
/**
|
|
* Builds the master - child, predecessor - successor and prerequisite links
|
|
* and modifies the entry texts accordingly.
|
|
*/
|
|
public static void linkFeats() {
|
|
FeatEntry other = null;
|
|
Data_2da feats2da = twoDA.get("feat");
|
|
boolean allChildrenEpic, allChildrenClassFeat;
|
|
|
|
// Link normal feats to each other and to masterfeats
|
|
for (FeatEntry check : feats.values()) {
|
|
if (verbose) System.out.println("Linking feat " + check.name);
|
|
// Link to master
|
|
if (!feats2da.getEntry("MASTERFEAT", check.entryNum).equals("****")) {
|
|
try {
|
|
other = masterFeats.get(Integer.parseInt(feats2da.getEntry("MASTERFEAT", check.entryNum)));
|
|
other.childFeats.put(check.name, check);
|
|
check.master = other;
|
|
if (check.isEpic) other.isEpic = true;
|
|
if (!check.isClassFeat) other.isClassFeat = false;
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " contains an invalid MASTERFEAT entry");
|
|
} catch (NullPointerException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " MASTERFEAT points to a nonexistent masterfeat entry");
|
|
}
|
|
}
|
|
|
|
// Handle prerequisites
|
|
buildPrerequisites(check, feats2da);
|
|
|
|
// Handle successor feat, if any
|
|
if (!feats2da.getEntry("SUCCESSOR", check.entryNum).equals("****")) {
|
|
try {
|
|
other = feats.get(Integer.parseInt(feats2da.getEntry("SUCCESSOR", check.entryNum)));
|
|
// Check for feats that have themselves as successor
|
|
if (other == check)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " has itself as successor");
|
|
other.isSuccessor = true;
|
|
check.successor = other;
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " contains an invalid SUCCESSOR entry");
|
|
} catch (NullPointerException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " SUCCESSOR points to a nonexistent feat entry");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Masterfeat categorisation
|
|
for (FeatEntry check : masterFeats.values()) {
|
|
if (verbose) System.out.println("Linking masterfeat " + check.name);
|
|
allChildrenEpic = allChildrenClassFeat = true;
|
|
for (FeatEntry child : check.childFeats.values()) {
|
|
if (!child.isEpic) allChildrenEpic = false;
|
|
if (!child.isClassFeat) allChildrenClassFeat = false;
|
|
}
|
|
|
|
check.allChildrenClassFeat = allChildrenClassFeat;
|
|
check.allChildrenEpic = allChildrenEpic;
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
/**
|
|
* Links a feat and it's prerequisites.
|
|
* Separated from the linkFeats method for improved readability.
|
|
*
|
|
* @param check the feat entry to be examined
|
|
* @param feats2da the data structure representing feat.2da
|
|
*/
|
|
private static void buildPrerequisites(FeatEntry check, Data_2da feats2da) {
|
|
FeatEntry andReq1 = null, andReq2 = null, orReq = null;
|
|
String andReq1Num = feats2da.getEntry("PREREQFEAT1", check.entryNum),
|
|
andReq2Num = feats2da.getEntry("PREREQFEAT2", check.entryNum);
|
|
String[] orReqs = {feats2da.getEntry("OrReqFeat0", check.entryNum),
|
|
feats2da.getEntry("OrReqFeat1", check.entryNum),
|
|
feats2da.getEntry("OrReqFeat2", check.entryNum),
|
|
feats2da.getEntry("OrReqFeat3", check.entryNum),
|
|
feats2da.getEntry("OrReqFeat4", check.entryNum)};
|
|
|
|
/* Handle AND prerequisite feats */
|
|
// Some paranoia about bad entries
|
|
if (!andReq1Num.equals("****")) {
|
|
try {
|
|
andReq1 = feats.get(Integer.parseInt(andReq1Num));
|
|
if (andReq1 == null)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " PREREQFEAT1 points to a nonexistent feat entry");
|
|
else if (andReq1 == check)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " has itself as PREREQFEAT1");
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " contains an invalid PREREQFEAT1 entry");
|
|
}
|
|
}
|
|
if (!andReq2Num.equals("****")) {
|
|
try {
|
|
andReq2 = feats.get(Integer.parseInt(andReq2Num));
|
|
if (andReq2 == null)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " PREREQFEAT2 points to a nonexistent feat entry");
|
|
else if (andReq2 == check)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " has itself as PREREQFEAT2");
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " contains an invalid PREREQFEAT2 entry");
|
|
}
|
|
}
|
|
|
|
// Check if we had at least one valid entry. If so, link
|
|
if (andReq1 != null || andReq2 != null) {
|
|
if (andReq1 != null) {
|
|
check.andRequirements.put(andReq1.name, andReq1);
|
|
andReq1.requiredForFeats.put(check.name, check);
|
|
}
|
|
if (andReq2 != null) {
|
|
check.andRequirements.put(andReq2.name, andReq2);
|
|
andReq2.requiredForFeats.put(check.name, check);
|
|
}
|
|
}
|
|
|
|
|
|
/* Handle OR prerequisite feats */
|
|
// First, check if there are any
|
|
boolean hasOrReqs = false;
|
|
for (String orReqCheck : orReqs)
|
|
if (!orReqCheck.equals("****")) hasOrReqs = true;
|
|
|
|
if (hasOrReqs) {
|
|
// Loop through each req and see if it's a valid link
|
|
for (int i = 0; i < orReqs.length; i++) {
|
|
if (!orReqs[i].equals("****")) {
|
|
try {
|
|
orReq = feats.get(Integer.parseInt(orReqs[i]));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " contains an invalid OrReqFeat" + i + " entry");
|
|
}
|
|
if (orReq != null) {
|
|
if (orReq == check)
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " has itself as OrReqFeat" + i);
|
|
check.orRequirements.put(orReq.name, orReq);
|
|
orReq.requiredForFeats.put(check.name, check);
|
|
} else
|
|
err_pr.println("Error: Feat " + check.entryNum + ": " + check.name + " OrReqFeat" + i + " points to a nonexistent feat entry");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles creation of the domain pages.
|
|
* Kills page generation on bad strref for name.
|
|
* Other errors are logged and prevent page write
|
|
*/
|
|
public static void doDomains() {
|
|
String domainPath = contentPath + "domains" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
icon = null,
|
|
path = null;
|
|
List<SpellEntry> spellList = null;
|
|
FeatEntry grantedFeat = null;
|
|
SpellEntry grantedSpell = null;
|
|
boolean errored;
|
|
|
|
domains = new HashMap<Integer, DomainEntry>();
|
|
Data_2da domains2da = twoDA.get("domains");
|
|
|
|
for (int i = 0; i < domains2da.getEntryCount(); i++) {
|
|
// Skip blank rows
|
|
if (domains2da.getEntry("LABEL", i).equals("****")) continue;
|
|
errored = false;
|
|
|
|
// Get the name and validate it
|
|
name = tlk.get(domains2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Printing page for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for domain " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the entry data
|
|
text = htmlizeTLK(tlk.get(domains2da.getEntry("Description", i)));
|
|
// Check the description validity
|
|
if (tlk.get(domains2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for domain " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Add in the icon
|
|
icon = domains2da.getEntry("Icon", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for domain " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Add a link to the granted feat
|
|
try {
|
|
grantedFeat = feats.get(Integer.parseInt(domains2da.getEntry("GrantedFeat", i)));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid entry in GrantedFeat of domain " + i + ": " + name);
|
|
errored = true;
|
|
} catch (NullPointerException e) {
|
|
err_pr.println("Error: GrantedFeat entry for domain " + i + ": " + name + " points to non-existent feat: " + domains2da.getEntry("GrantedFeat", i));
|
|
errored = true;
|
|
}
|
|
|
|
// Add links to the granted spells
|
|
spellList = new ArrayList<SpellEntry>();
|
|
for (int j = 1; j <= 9; j++) {
|
|
// Skip blanks
|
|
if (domains2da.getEntry("Level_" + j, i).equals("****")) continue;
|
|
try {
|
|
grantedSpell = spells.get(Integer.parseInt(domains2da.getEntry("Level_" + j, i)));
|
|
if (grantedSpell != null)
|
|
spellList.add(grantedSpell);
|
|
else {
|
|
err_pr.println("Error: Level_" + j + " entry for domain " + i + ": " + name + " points to non-existent spell: " + domains2da.getEntry("Level_" + j, i));
|
|
errored = true;
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid entry in Level_" + j + " of domain " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
}
|
|
|
|
// Build path and print
|
|
path = domainPath + i + ".html";
|
|
if (!errored || tolErr) {
|
|
domains.put(i, new DomainEntry(name, text, icon, path, i, grantedFeat, spellList));
|
|
} else
|
|
err_pr.println("Error: Failed to generate entry for domain " + i + ": " + name);
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles creation of the race pages.
|
|
*/
|
|
public static void doRaces() {
|
|
String racePath = contentPath + "races" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
path = null;
|
|
TreeMap<String, FeatEntry> featList = null;
|
|
FeatEntry grantedFeat = null;
|
|
Data_2da featTable = null;
|
|
boolean errored;
|
|
|
|
races = new HashMap<Integer, RaceEntry>();
|
|
Data_2da racialtypes2da = twoDA.get("racialtypes");
|
|
|
|
for (int i = 0; i < racialtypes2da.getEntryCount(); i++) {
|
|
// Skip non-player races
|
|
if (!racialtypes2da.getEntry("PlayerRace", i).equals("1")) continue;
|
|
errored = false;
|
|
try {
|
|
// Get the name and validate it
|
|
name = tlk.get(racialtypes2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Printing page for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for race " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the entry data
|
|
text = htmlizeTLK(tlk.get(racialtypes2da.getEntry("Description", i)));
|
|
// Check the description validity
|
|
if (tlk.get(racialtypes2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for race " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
|
|
// Add links to the racial feats
|
|
try {
|
|
featTable = twoDA.get(racialtypes2da.getEntry("FeatsTable", i));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read RACE_FEAT_*.2da for race " + i + ": " + name + ":\n" + e);
|
|
}
|
|
|
|
featList = new TreeMap<String, FeatEntry>();
|
|
for (int j = 0; j < featTable.getEntryCount(); j++) {
|
|
try {
|
|
grantedFeat = feats.get(Integer.parseInt(featTable.getEntry("FeatIndex", j)));
|
|
featList.put(grantedFeat.name, grantedFeat);
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid entry in FeatIndex line " + j + " of " + featTable.getName());
|
|
errored = true;
|
|
} catch (NullPointerException e) {
|
|
err_pr.println("Error: FeatIndex line " + j + " of " + featTable.getName() + " points to non-existent feat: " + featTable.getEntry("FeatIndex", j));
|
|
errored = true;
|
|
}
|
|
}
|
|
|
|
// Build path and print
|
|
path = racePath + i + ".html";
|
|
if (!errored || tolErr) {
|
|
races.put(i, new RaceEntry(name, text, path, i, featList));
|
|
} else
|
|
throw new PageGenerationException("Error(s) encountered while creating page");
|
|
} catch (PageGenerationException e) {
|
|
err_pr.println("Error: Failed to print page for race " + i + ": " + name + ":\n" + e);
|
|
}
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles creation of the class pages.
|
|
* Subsections handled by several following methods.
|
|
*/
|
|
public static void doClasses() {
|
|
String baseClassPath = contentPath + "base_classes" + fileSeparator,
|
|
prestigeClassPath = contentPath + "prestige_classes" + fileSeparator;
|
|
String name = null,
|
|
text = null,
|
|
icon = null,
|
|
path = null,
|
|
temp = null;
|
|
List<String[]> babSav = null;
|
|
Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>> skillList = null;
|
|
Tuple<List<Integer>, Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>> featList = null;
|
|
List<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>> magics = null;
|
|
|
|
boolean errored;
|
|
|
|
classes = new HashMap<Integer, ClassEntry>();
|
|
Data_2da classes2da = twoDA.get("classes");
|
|
|
|
for (int i = 0; i < classes2da.getEntryCount(); i++) {
|
|
// Skip non-player classes
|
|
if (!classes2da.getEntry("PlayerClass", i).equals("1")) continue;
|
|
errored = false;
|
|
try {
|
|
name = tlk.get(classes2da.getEntry("Name", i));
|
|
if (verbose) System.out.println("Printing page for " + name);
|
|
if (name.equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid name for class " + i);
|
|
errored = true;
|
|
}
|
|
|
|
// Build the entry data
|
|
text = htmlizeTLK(tlk.get(classes2da.getEntry("Description", i)));
|
|
// Check the description validity
|
|
if (tlk.get(classes2da.getEntry("Description", i)).equals(badStrRef)) {
|
|
err_pr.println("Error: Invalid description for class " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
|
|
// Add in the icon
|
|
icon = classes2da.getEntry("Icon", i);
|
|
if (icon.equals("****")) {
|
|
err_pr.println("Error: Icon not defined for class " + i + ": " + name);
|
|
errored = true;
|
|
}
|
|
icon = Icons.buildIcon(icon);
|
|
|
|
// Add in the BAB and saving throws table
|
|
babSav = buildBabAndSaveList(classes2da, i);
|
|
|
|
// Add in the skills table
|
|
skillList = buildSkillList(classes2da, i);
|
|
|
|
// Add in the feat table
|
|
featList = buildClassFeatList(classes2da, i);
|
|
|
|
// Add in the spells / powers table
|
|
magics = buildClassMagicList(classes2da, i);
|
|
|
|
/* Check whether this is a base or a prestige class. No prestige
|
|
* class should give exp penalty (nor should any base class not give it),
|
|
* so it gan be used as an indicator.
|
|
*/
|
|
temp = classes2da.getEntry("XPPenalty", i);
|
|
if (!(temp.equals("0") || temp.equals("1"))) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid List XPPenalty in classes.2da on row " + i + ": " + temp);
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid XPPenalty entry in classes.2da on row " + i + ": " + temp);
|
|
}
|
|
if (temp.equals("1"))
|
|
path = baseClassPath + i + ".html";
|
|
else
|
|
path = prestigeClassPath + i + ".html";
|
|
|
|
if (!errored || tolErr) {
|
|
classes.put(i, new ClassEntry(name, text, icon, path, i, temp.equals("1"),
|
|
babSav, skillList, featList, magics));
|
|
} else
|
|
throw new PageGenerationException("Error(s) encountered while creating page");
|
|
} catch (PageGenerationException e) {
|
|
err_pr.println("Error: Failed to print page for class " + i + ": " + name + ":\n" + e);
|
|
}
|
|
}
|
|
System.gc();
|
|
}
|
|
|
|
/**
|
|
* Constructs a list of arrays containing the BAB and saving throw values
|
|
* for the given class.
|
|
*
|
|
* @param classes2da data structure wrapping classes.2da
|
|
* @param entryNum number of the entry to generate list for
|
|
* @return List<String [ ]> containing the values. BAB first, then saving throws (Fort, Ref, Will).
|
|
* @throws PageGenerationException if there is an error while generating the list and error tolerance is off
|
|
*/
|
|
private static List<String[]> buildBabAndSaveList(Data_2da classes2da, int entryNum) {
|
|
Data_2da babTable = null,
|
|
saveTable = null;
|
|
try {
|
|
babTable = twoDA.get(classes2da.getEntry("AttackBonusTable", entryNum));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read CLS_ATK_*.2da for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)) + ":\n" + e);
|
|
}
|
|
try {
|
|
saveTable = twoDA.get(classes2da.getEntry("SavingThrowTable", entryNum));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read CLS_SAVTHR_*.2da for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)) + ":\n" + e);
|
|
}
|
|
|
|
/* Determine maximum level to print bab & save values to
|
|
* The maximum level of the class, or the last non-epic level
|
|
* whichever is lower
|
|
*/
|
|
int maxToPrint = 0, maxLevel = 0, epicLevel = 0;
|
|
try {
|
|
maxLevel = Integer.parseInt(classes2da.getEntry("MaxLevel", entryNum));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr)
|
|
err_pr.println("Error: Invalid MaxLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
else
|
|
throw new PageGenerationException("Invalid MaxLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
try {
|
|
epicLevel = Integer.parseInt(classes2da.getEntry("EpicLevel", entryNum));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr)
|
|
err_pr.println("Error: Invalid EpicLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
else
|
|
throw new PageGenerationException("Invalid EpicLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
|
|
// base classes have special notation for the epic level limit
|
|
if (epicLevel == -1)
|
|
maxToPrint = 20;
|
|
else
|
|
maxToPrint = maxLevel > epicLevel ? epicLevel : maxLevel;
|
|
|
|
// If the class has any pre-epic levels
|
|
List<String[]> toReturn = new ArrayList<String[]>(maxToPrint);
|
|
if (maxToPrint > 0) {
|
|
// Start building the table
|
|
for (int i = 0; i < maxToPrint; i++) {
|
|
toReturn.add(new String[]{
|
|
babTable.getEntry("BAB", i),
|
|
saveTable.getEntry("FortSave", i),
|
|
saveTable.getEntry("RefSave", i),
|
|
saveTable.getEntry("WillSave", i)
|
|
});
|
|
}
|
|
}
|
|
|
|
return toReturn;
|
|
}
|
|
|
|
/**
|
|
* Constructs a list of the class and cross-class skills of the
|
|
* given class
|
|
*
|
|
* @param classes2da data structure wrapping classes.2da
|
|
* @param entryNum number of the entry to generate table for
|
|
* @return Tuple<TreeMap < String, GenericEntry>, TreeMap<String, GenericEntry>>
|
|
* containing the class and cross-class skills of the given class. The
|
|
* first tuple member contains the class skills, the second cross-class.
|
|
* The map key is the name of the skill.
|
|
* @throws PageGenerationException if there is an error while generating the list and error tolerance is off
|
|
*/
|
|
private static Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>> buildSkillList(Data_2da classes2da, int entryNum) {
|
|
Data_2da skillTable = null;
|
|
try {
|
|
skillTable = twoDA.get(classes2da.getEntry("SkillsTable", entryNum));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read CLS_SKILL_*.2da for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)) + ":\n" + e);
|
|
}
|
|
|
|
Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>> toReturn =
|
|
new Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>>(
|
|
new TreeMap<String, GenericEntry>(), new TreeMap<String, GenericEntry>());
|
|
String skillNum = null;
|
|
GenericEntry skillEntry = null;
|
|
|
|
for (int i = 0; i < skillTable.getEntryCount(); i++) {
|
|
skillNum = skillTable.getEntry("ClassSkill", i);
|
|
// Yet more validity checking :P
|
|
if (!(skillNum.equals("0") || skillNum.equals("1"))) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid ClassSkill entry in " + skillTable.getName() + " on row " + i);
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid ClassSkill entry in " + skillTable.getName() + " on row " + i);
|
|
}
|
|
|
|
try {
|
|
skillEntry = skills.get(Integer.parseInt(skillTable.getEntry("SkillIndex", i)));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid SkillIndex entry in " + skillTable.getName() + " on row " + i);
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid SkillIndex entry in " + skillTable.getName() + " on row " + i);
|
|
}
|
|
if (skillEntry == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: SkillIndex entry in " + skillTable.getName() + " on row " + i + " points to non-existent skill");
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("SkillIndex entry in " + skillTable.getName() + " on row " + i + " points to non-existent skill");
|
|
}
|
|
|
|
if (skillNum.equals("1"))
|
|
toReturn.e1.put(skillEntry.name, skillEntry); // Class skill
|
|
else
|
|
toReturn.e2.put(skillEntry.name, skillEntry); // Cross-class skill
|
|
}
|
|
|
|
return toReturn;
|
|
}
|
|
|
|
/**
|
|
* Constructs a pair of granted feat, selectable feat lists for
|
|
* the given class.
|
|
*
|
|
* @param classes2da data structure wrapping classes.2da
|
|
* @param entryNum number of the entry to generate list for
|
|
* @return Tuple<List < TreeMap < String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>.
|
|
* The first list contains the granted feats, the second selectable feats.
|
|
* Each list consists of TreeMaps containing the feats that are related to
|
|
* the list indexth (+1) level, keyed by feat name.
|
|
* @throws PageGenerationException if there is an error while generating the lists and error tolerance is off
|
|
*/
|
|
private static Tuple<List<Integer>, Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>>
|
|
buildClassFeatList(Data_2da classes2da, int entryNum) {
|
|
Data_2da featTable = null,
|
|
bonusFeatTable = null;
|
|
ArrayList<TreeMap<String, FeatEntry>> grantedFeatList = new ArrayList<TreeMap<String, FeatEntry>>(0),
|
|
selectableFeatList = new ArrayList<TreeMap<String, FeatEntry>>(0);
|
|
ArrayList<Integer> bonusFeatCounts = new ArrayList<Integer>();
|
|
HashSet<FeatEntry> masterFeatsUsed = new HashSet<FeatEntry>();
|
|
String listNum = null;
|
|
FeatEntry classFeat = null;
|
|
int maxLevel, epicLevel, grantedLevel;
|
|
|
|
// Attempt to load the class feats table
|
|
try {
|
|
featTable = twoDA.get(classes2da.getEntry("FeatsTable", entryNum));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read CLS_FEAT_*.2da for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)) + ":\n" + e);
|
|
}
|
|
// Attempt to load the class bonus feat slots table
|
|
try {
|
|
bonusFeatTable = twoDA.get(classes2da.getEntry("BonusFeatsTable", entryNum));
|
|
} catch (TwoDAReadException e) {
|
|
throw new PageGenerationException("Failed to read CLS_BFEAT_*.2da for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)) + ":\n" + e);
|
|
}
|
|
// Attempt to read the class epic level
|
|
try {
|
|
epicLevel = Integer.parseInt(classes2da.getEntry("EpicLevel", entryNum));
|
|
} catch (NumberFormatException e) {
|
|
throw new PageGenerationException("Invalid EpicLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
// Attempt to read the class maximum level
|
|
try {
|
|
if (epicLevel == -1)
|
|
maxLevel = 40;
|
|
else
|
|
maxLevel = Integer.parseInt(classes2da.getEntry("MaxLevel", entryNum));
|
|
} catch (NumberFormatException e) {
|
|
throw new PageGenerationException("Invalid MaxLevel entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
|
|
// Base classes have EpicLevel defined as -1, but become epic at L20
|
|
if (epicLevel == -1) epicLevel = 20;
|
|
// Sanity check
|
|
else if (epicLevel > maxLevel) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: EpicLevel value(" + epicLevel + ") greater than MaxLevel value(" + maxLevel + ") for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
epicLevel = maxLevel;
|
|
} else
|
|
throw new PageGenerationException("EpicLevel value(" + epicLevel + ") greater than MaxLevel value(" + maxLevel + ") for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
|
|
|
|
// Init the lists
|
|
for (int i = 0; i < maxLevel; i++) grantedFeatList.add(null);
|
|
for (int i = 0; i < maxLevel; i++) selectableFeatList.add(null);
|
|
|
|
|
|
// Build a level-sorted list of feats
|
|
for (int i = 0; i < featTable.getEntryCount(); i++) {
|
|
// Skip empty rows and comments
|
|
if (featTable.getEntry("FeatLabel", i).equals("****") ||
|
|
featTable.getEntry("FeatIndex", i).equals("****"))
|
|
continue;
|
|
|
|
// Read the list number and validate
|
|
listNum = featTable.getEntry("List", i);
|
|
if (!(listNum.equals("0") || listNum.equals("1") || listNum.equals("2") || listNum.equals("3"))) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid List entry in " + featTable.getName() + " on row " + i + ": " + listNum);
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid List entry in " + featTable.getName() + " on row " + i + ": " + listNum);
|
|
}
|
|
|
|
// Read the level granted on and validate
|
|
try {
|
|
grantedLevel = Integer.parseInt(featTable.getEntry("GrantedOnLevel", i));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid GrantedOnLevel entry in " + featTable.getName() + " on row " + i + ": " + featTable.getEntry("GrantedOnLevel", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid GrantedOnLevel entry in " + featTable.getName() + " on row " + i + ": " + featTable.getEntry("GrantedOnLevel", i));
|
|
}
|
|
|
|
// Complain about a semantic error
|
|
if (listNum.equals("3") && grantedLevel == -1) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: List value '3' combined with GrantedOnLevel value '-1' in " + featTable.getName() + " on row " + i);
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("List value '3' combined with GrantedOnLevel value '-1' in " + featTable.getName() + " on row " + i);
|
|
}
|
|
|
|
// Get the feat on this row and validate
|
|
try {
|
|
classFeat = feats.get(Integer.parseInt(featTable.getEntry("FeatIndex", i)));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid FeatIndex entry in " + featTable.getName() + " on row " + i + ": " + featTable.getEntry("FeatIndex", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid FeatIndex entry in " + featTable.getName() + " on row " + i + ": " + featTable.getEntry("FeatIndex", i));
|
|
}
|
|
if (classFeat == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: FeatIndex entry in " + featTable.getName() + " on row " + i + " points to non-existent feat: " + featTable.getEntry("FeatIndex", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("FeatIndex entry in " + featTable.getName() + " on row " + i + " points to non-existent feat: " + featTable.getEntry("FeatIndex", i));
|
|
}
|
|
|
|
|
|
// Skip feats that can never be gotten
|
|
if (grantedLevel > 40) continue;
|
|
if (grantedLevel > maxLevel) {
|
|
// This is never a fatal error. It's merely bad practice to place the feat outside reachable bounds, but not obviously so (ex, value of 99)
|
|
err_pr.println("Error: GrantedOnLevel entry in " + featTable.getName() + " on row " + i + " is greater than the class's maximum level, but not obviously unreachable: " + grantedLevel + " vs. " + maxLevel);
|
|
continue;
|
|
}
|
|
|
|
// If the feat has a master, replace it with the master in the listing to prevent massive spammage
|
|
if (classFeat.master != null) {
|
|
// Only add masterfeats to the list once.
|
|
if (masterFeatsUsed.contains(classFeat.master)) continue;
|
|
masterFeatsUsed.add(classFeat.master);
|
|
classFeat = classFeat.master;
|
|
}
|
|
|
|
// Freely selectable feats become available at L1
|
|
if (grantedLevel == -1) {
|
|
if (classFeat.isEpic) {
|
|
// Epic feats should be shown to become available on the level that is the first after the class's normal progression ends
|
|
// Sanity check here against bad class entries causing index violations
|
|
grantedLevel = Math.min(epicLevel + 1, maxLevel);
|
|
} else
|
|
grantedLevel = 1;
|
|
}
|
|
grantedLevel -= 1; // Adjust to 0-based array index
|
|
// Differentiate by automatically granted or selectable
|
|
if (listNum.equals("3")) {
|
|
// Create the map if missing
|
|
if (grantedFeatList.get(grantedLevel) == null)
|
|
grantedFeatList.set(grantedLevel, new TreeMap<String, FeatEntry>());
|
|
|
|
// Add the feat to the map
|
|
grantedFeatList.get(grantedLevel).put(classFeat.name, classFeat);
|
|
} else {
|
|
// Create the map if missing
|
|
if (selectableFeatList.get(grantedLevel) == null)
|
|
selectableFeatList.set(grantedLevel, new TreeMap<String, FeatEntry>());
|
|
|
|
// Add the feat to the map
|
|
selectableFeatList.get(grantedLevel).put(classFeat.name, classFeat);
|
|
}
|
|
}
|
|
|
|
// Make sure there are enough entries in the bonus feat table
|
|
if (bonusFeatTable.getEntryCount() < maxLevel) {
|
|
throw new PageGenerationException("Too few entries in class bonus feat table " + bonusFeatTable.getName() + ": " + bonusFeatTable.getEntryCount() + ". Need " + maxLevel);
|
|
}
|
|
|
|
for (int i = 0; i < maxLevel; i++) {
|
|
try {
|
|
bonusFeatCounts.add(Integer.parseInt(bonusFeatTable.getEntry("Bonus", i)));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Bonus entry in " + bonusFeatTable.getName() + " on row " + i + ": " + bonusFeatTable.getEntry("Bonus", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Bonus entry in " + bonusFeatTable.getName() + " on row " + i + ": " + bonusFeatTable.getEntry("Bonus", i));
|
|
}
|
|
}
|
|
|
|
return new Tuple<List<Integer>, Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>>(
|
|
bonusFeatCounts,
|
|
new Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>(
|
|
grantedFeatList,
|
|
selectableFeatList));
|
|
}
|
|
|
|
private static int getIntFrom2DAColumn(Data_2da table, String column, int row) {
|
|
return getIntFrom2DAColumn(table, column, row, false);
|
|
}
|
|
|
|
private static int getIntFrom2DAColumn(Data_2da table, String column, int row, boolean mayBeInvalid) {
|
|
try {
|
|
return Integer.parseInt(table.getEntry(column, row));
|
|
} catch (NumberFormatException e) {
|
|
if (mayBeInvalid) {
|
|
// Caller indicated that it's okay if this value doesn't exist
|
|
return -1;
|
|
}
|
|
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid " + column + " entry in " + table.getName() + " on row " + row + ": " + table.getEntry(column, row));
|
|
return -1;
|
|
} else
|
|
throw new PageGenerationException("Invalid " + column + " entry in " + table.getName() + " on row " + row + ": " + table.getEntry(column, row));
|
|
}
|
|
}
|
|
|
|
private static Map<Integer, String> baseClassIdsToSpellColumns = new HashMap<Integer, String>() {{
|
|
put(1, "Bard");
|
|
put(2, "Cleric");
|
|
put(3, "Druid");
|
|
put(6, "Paladin");
|
|
put(7, "Ranger");
|
|
put(9, "Wiz_Sorc");
|
|
put(10, "Wiz_Sorc");
|
|
}};
|
|
|
|
/**
|
|
* Constructs lists of the magics available to the given class.
|
|
* The entries are ordered by spell / power level
|
|
*
|
|
* @param classes2da data structure wrapping classes.2da
|
|
* @param entryNum number of the entry to generate table for
|
|
* @return List<Tuple < Tuple < String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>>.
|
|
* Each list entry contains one magic type. The first tuple member consists of the
|
|
* name of magic system, name of spell-equivalent pari. The second member contains
|
|
* the magic entries. The Integer-keyed TreeMaps contain the spells
|
|
* of each level. The integers are the spell levels.
|
|
* @throws PageGenerationException if there is an error while generating the list and error tolerance is off
|
|
*/
|
|
private static List<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>> buildClassMagicList(Data_2da classes2da, int entryNum) {
|
|
List<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>> toReturn =
|
|
new ArrayList<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>>();
|
|
String classAbrev = null;
|
|
Data_2da spellList = null,
|
|
powerList = null,
|
|
utterList = null,
|
|
//invocations and maneuvers are a bit different because they don't have their own name column
|
|
invocList = null,
|
|
maneuList = null,
|
|
spells2da = twoDA.get("spells");
|
|
|
|
|
|
// Check for correctly formed table name
|
|
if (!classes2da.getEntry("FeatsTable", entryNum).toLowerCase().startsWith("cls_feat_")) {
|
|
throw new PageGenerationException("Malformed FeatsTable entry for class " + entryNum + ": " + tlk.get(classes2da.getEntry("Name", entryNum)));
|
|
}
|
|
|
|
// Extract the class abbreviation
|
|
classAbrev = classes2da.getEntry("FeatsTable", entryNum).toLowerCase().substring(9);
|
|
|
|
// Attempt to load the class and power 2das - If these fail, assume it was just due to non-existent file
|
|
try {
|
|
spellList = twoDA.get("cls_spcr_" + classAbrev);
|
|
} catch (TwoDAReadException e) { /* Ensure nullness */
|
|
powerList = null;
|
|
}
|
|
try {
|
|
powerList = twoDA.get("cls_psipw_" + classAbrev);
|
|
} catch (TwoDAReadException e) { /* Ensure nullness */
|
|
powerList = null;
|
|
}
|
|
try {
|
|
utterList = twoDA.get("cls_" + classAbrev + "_utter");
|
|
} catch (TwoDAReadException e) { /* Ensure nullness */
|
|
utterList = null;
|
|
}
|
|
try {
|
|
invocList = twoDA.get("cls_inv_" + classAbrev);
|
|
} catch (TwoDAReadException e) { /* Ensure nullness */
|
|
invocList = null;
|
|
}
|
|
try {
|
|
maneuList = twoDA.get("cls_move_" + classAbrev);
|
|
} catch (TwoDAReadException e) { /* Ensure nullness */
|
|
maneuList = null;
|
|
}
|
|
|
|
// Do spellbook
|
|
if (spellList == null && baseClassIdsToSpellColumns.containsKey(entryNum)) {
|
|
// There's no custom spellbook for this class, so pull the info from spells.2da
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry spell = null;
|
|
String classColumnName = baseClassIdsToSpellColumns.get(entryNum);
|
|
int level;
|
|
|
|
for (int i = 0; i < spells2da.getEntryCount(); i++) {
|
|
// Make sure the Level entry is a number
|
|
level = getIntFrom2DAColumn(spells2da, classColumnName, i, true);
|
|
if (level == -1) continue;
|
|
|
|
spell = spells.get(i);
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(spell.name, spell);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_SPELLBOOKTXT], curLanguageData[LANGDATA_SPELLSTXT]),
|
|
levelLists));
|
|
} else if (spellList != null) {
|
|
// Map of level numbers to maps of spell names to html links
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry spell = null;
|
|
int level;
|
|
|
|
for (int i = 0; i < spellList.getEntryCount(); i++) {
|
|
// Make sure the Level entry is a number
|
|
level = getIntFrom2DAColumn(spellList, "Level", i);
|
|
if (level == -1) continue;
|
|
|
|
// Make sure the SpellID is valid
|
|
int spellId = getIntFrom2DAColumn(spellList, "SpellID", i);
|
|
if (spellId == -1) continue;
|
|
spell = spells.get(spellId);
|
|
|
|
if (spell == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: SpellID entry in " + spellList.getName() + " on row " + i + " points at nonexistent spell: " + spellList.getEntry("SpellID", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("SpellID entry in " + spellList.getName() + " on row " + i + " points at nonexistent spell: " + spellList.getEntry("SpellID", i));
|
|
}
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(spell.name, spell);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_SPELLBOOKTXT], curLanguageData[LANGDATA_SPELLSTXT]),
|
|
levelLists));
|
|
}
|
|
|
|
// Do psionics
|
|
if (powerList != null) {
|
|
// Map of level numbers to maps of spell names to html links
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry power = null;
|
|
int level;
|
|
|
|
for (int i = 0; i < powerList.getEntryCount(); i++) {
|
|
// Skip rows that do not define a power
|
|
if (powerList.getEntry("Level", i).equals("****"))
|
|
continue;
|
|
|
|
level = getIntFrom2DAColumn(powerList, "Level", i);
|
|
if (level == -1) continue;
|
|
|
|
// Make sure the SpellID is valid
|
|
int realSpellId = getIntFrom2DAColumn(powerList, "RealSpellID", i);
|
|
if (realSpellId < 0) continue;
|
|
power = spells.get(realSpellId);
|
|
|
|
if (power == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Unable to map Name entry in " + powerList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(powerList.getEntry("Label", i)));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Unable to map Name entry in " + powerList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(powerList.getEntry("Name", i)));
|
|
}
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(power.name, power);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_PSIONICPOWERSTXT], curLanguageData[LANGDATA_POWERTXT]),
|
|
levelLists));
|
|
}
|
|
|
|
// Do truenaming
|
|
if (utterList != null) {
|
|
// Map of level numbers to maps of spell names to html links
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry utterance = null;
|
|
int level;
|
|
|
|
for (int i = 0; i < utterList.getEntryCount(); i++) {
|
|
// Skip rows that do not define a power
|
|
if (utterList.getEntry("Level", i).equals("****"))
|
|
continue;
|
|
|
|
// Make sure the Level entry is a number
|
|
try {
|
|
level = Integer.parseInt(utterList.getEntry("Level", i));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Level entry in " + utterList.getName() + " on row " + i + ": " + utterList.getEntry("Level", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Level entry in " + utterList.getName() + " on row " + i + ": " + utterList.getEntry("Level", i));
|
|
}
|
|
|
|
// Make sure the SpellID is valid
|
|
utterance = null;
|
|
try {
|
|
// Attempt to get the spell entry via a mapping of power names to spellIDs
|
|
utterance = spells.get(utterMap.get(tlk.get(Integer.parseInt(utterList.getEntry("Name", i)))));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Name entry in " + utterList.getName() + " on row " + i + ": " + utterList.getEntry("Name", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Name entry in " + utterList.getName() + " on row " + i + ": " + utterList.getEntry("Name", i));
|
|
}
|
|
if (utterance == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Unable to map Name entry in " + utterList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(utterList.getEntry("Name", i)));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Unable to map Name entry in " + utterList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(utterList.getEntry("Name", i)));
|
|
}
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(utterance.name, utterance);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_TRUENAMEUTTERANCETXT], curLanguageData[LANGDATA_UTTERANCETXT]),
|
|
levelLists));
|
|
}
|
|
|
|
// Do invocations
|
|
if (invocList != null) {
|
|
// Map of level numbers to maps of spell names to html links
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry invocation = null;
|
|
int level;
|
|
|
|
for (int i = 0; i < invocList.getEntryCount(); i++) {
|
|
// Skip rows that do not define a power
|
|
if (invocList.getEntry("Level", i).equals("****"))
|
|
continue;
|
|
|
|
// Make sure the Level entry is a number
|
|
try {
|
|
level = Integer.parseInt(invocList.getEntry("Level", i));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Level entry in " + invocList.getName() + " on row " + i + ": " + invocList.getEntry("Level", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Level entry in " + invocList.getName() + " on row " + i + ": " + invocList.getEntry("Level", i));
|
|
}
|
|
|
|
// Make sure the SpellID is valid
|
|
invocation = null;
|
|
try {
|
|
// Look in spells.2da for name
|
|
invocation = spells.get(invMap.get(tlk.get(spells2da.getEntry("Name", invocList.getEntry("RealSpellID", i)))));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Name entry in " + invocList.getName() + " on row " + i + ": " + invocList.getEntry("Name", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Name entry in " + invocList.getName() + " on row " + i + ": " + invocList.getEntry("Name", i));
|
|
}
|
|
if (invocation == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Unable to map Name entry in " + invocList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(invocList.getEntry("Name", i)));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Unable to map Name entry in " + invocList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(invocList.getEntry("Name", i)));
|
|
}
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(invocation.name, invocation);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_INVOCATIONTXT], curLanguageData[LANGDATA_INVOCATIONTXT]),
|
|
levelLists));
|
|
}
|
|
|
|
// Do maneuvers
|
|
if (maneuList != null) {
|
|
// Map of level numbers to maps of spell names to html links
|
|
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists = new TreeMap<Integer, TreeMap<String, SpellEntry>>();
|
|
SpellEntry maneuver = null;
|
|
int level;
|
|
|
|
for (int i = 0; i < maneuList.getEntryCount(); i++) {
|
|
// Skip rows that do not define a power
|
|
if (maneuList.getEntry("Level", i).equals("****"))
|
|
continue;
|
|
|
|
// Make sure the Level entry is a number
|
|
try {
|
|
level = Integer.parseInt(maneuList.getEntry("Level", i));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Level entry in " + maneuList.getName() + " on row " + i + ": " + maneuList.getEntry("Level", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Level entry in " + maneuList.getName() + " on row " + i + ": " + maneuList.getEntry("Level", i));
|
|
}
|
|
|
|
// Make sure the SpellID is valid
|
|
maneuver = null;
|
|
try {
|
|
// Look in spells.2da for name
|
|
maneuver = spells.get(maneuverMap.get(tlk.get(spells2da.getEntry("Name", maneuList.getEntry("RealSpellID", i)))));
|
|
} catch (NumberFormatException e) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Invalid Name entry in " + maneuList.getName() + " on row " + i + ": " + maneuList.getEntry("Name", i));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Invalid Name entry in " + maneuList.getName() + " on row " + i + ": " + maneuList.getEntry("Name", i));
|
|
}
|
|
if (maneuver == null) {
|
|
if (tolErr) {
|
|
err_pr.println("Error: Unable to map Name entry in " + maneuList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(maneuList.getEntry("Name", i)));
|
|
continue;
|
|
} else
|
|
throw new PageGenerationException("Unable to map Name entry in " + maneuList.getName() + " on row " + i + " to a spellEntry: " + tlk.get(maneuList.getEntry("Name", i)));
|
|
}
|
|
|
|
// If no map for this level yet, fill it in
|
|
if (!levelLists.containsKey(level))
|
|
levelLists.put(level, new TreeMap<String, SpellEntry>());
|
|
|
|
// Add the spell to the map
|
|
levelLists.get(level)
|
|
.put(maneuver.name, maneuver);
|
|
}
|
|
|
|
toReturn.add(new Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>(
|
|
new Tuple<String, String>(curLanguageData[LANGDATA_MANEUVERTXT], curLanguageData[LANGDATA_MANEUVERTXT]),
|
|
levelLists));
|
|
}
|
|
|
|
return toReturn;
|
|
}
|
|
}
|