forked from Jaysyn/PRC8
Updated Release Archive. Fixed Mage-killer prereqs. Removed old LETO & ConvoCC related files. Added organized spell scroll store. Fixed Gloura spellbook. Various TLK fixes. Reorganized Repo. Removed invalid user folders. Added DocGen back in.
1845 lines
85 KiB
Java
1845 lines
85 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() {
|
|
// A map of power name to class-specific spells.2da entry
|
|
psiPowMap = new HashMap<String, Integer>();
|
|
|
|
// Load cls_psipw_*.2da
|
|
String[] fileNames = new File("2da").list(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return name.toLowerCase().startsWith("cls_psipw_") &&
|
|
name.toLowerCase().endsWith(".2da");
|
|
}
|
|
});
|
|
|
|
listAMSEntries(fileNames, psiPowMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain a truenaming utterance's
|
|
*/
|
|
public static void listTruenameUtterances() {
|
|
// A map of power name to class-specific spells.2da entry
|
|
utterMap = new HashMap<String, Integer>();
|
|
|
|
// Load cls_*_utter.2da
|
|
String[] fileNames = new File("2da").list(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return name.toLowerCase().startsWith("cls_") &&
|
|
name.toLowerCase().endsWith("_utter.2da");
|
|
}
|
|
});
|
|
|
|
listAMSEntries(fileNames, utterMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain invocations
|
|
*/
|
|
public static void listInvocations() {
|
|
// A map of power name to class-specific spells.2da entry
|
|
invMap = new HashMap<String, Integer>();
|
|
|
|
// Load cls_*_utter.2da
|
|
String[] fileNames = new File("2da").list(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return name.toLowerCase().startsWith("cls_inv_") &&
|
|
name.toLowerCase().endsWith(".2da");
|
|
}
|
|
});
|
|
|
|
listAMSEntries(fileNames, invMap);
|
|
}
|
|
|
|
/**
|
|
* Creates a list of spells.2da rows that should contain maneuvers
|
|
*/
|
|
public static void listManeuvers() {
|
|
// A map of power name to class-specific spells.2da entry
|
|
maneuverMap = new HashMap<String, Integer>();
|
|
|
|
// Load cls_*_utter.2da
|
|
String[] fileNames = new File("2da").list(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return name.toLowerCase().startsWith("cls_move_") &&
|
|
name.toLowerCase().endsWith(".2da");
|
|
}
|
|
});
|
|
|
|
listAMSEntries(fileNames, 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(String[] fileNames, Map<String, Integer> storeMap) {
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
Data_2da[] list2das = new Data_2da[fileNames.length];
|
|
for (int i = 0; i < fileNames.length; i++)
|
|
//Strip out the ".2da" from the filenames before loading, since the loader function assumes it's missing
|
|
list2das[i] = twoDA.get(fileNames[i].replace(".2da", ""));
|
|
|
|
// Parse the 2das
|
|
for (Data_2da list2da : list2das) {
|
|
for (int i = 0; i < list2da.getEntryCount(); i++) {
|
|
// Column FeatID is used to determine if the row specifies the main entry of a power
|
|
if (!list2da.getEntry("FeatID", i).equals("****")) {
|
|
try {
|
|
//look up spells.2da name of the realspellid if we don't have a name column
|
|
if (list2da.getEntry("Name", i) == null) {
|
|
storeMap.put(tlk.get(spells2da.getEntry("Name", list2da.getEntry("RealSpellID", i))), Integer.parseInt(list2da.getEntry("RealSpellID", i)));
|
|
} else {
|
|
storeMap.put(tlk.get(list2da.getEntry("Name", i)), Integer.parseInt(list2da.getEntry("SpellID", i)));
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Invalid SpellID entry in " + list2da.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;
|
|
}
|
|
}
|