Updated Release Archive

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.
This commit is contained in:
Jaysyn904
2023-08-22 10:00:21 -04:00
parent 3acda03f30
commit 5914ed2ab5
22853 changed files with 57524 additions and 47307 deletions

Binary file not shown.

View File

@@ -0,0 +1,71 @@
package prc.autodoc;
import java.util.List;
import java.util.TreeMap;
/**
* Data structure for a class entry.
*/
public class ClassEntry extends GenericEntry {
/**
* If <code>true</code>, this class is a base class.
*/
public final boolean isBase;
/**
* List of BAB and saving throw values. Each list entry consists of the values
* for the list indexth level. Order: BAB, Fort, Ref, Will.
*/
public final List<String[]> babSav;
/**
* The list of this class's class and cross-class skills. First tuple member
* cotnaints the class skills, the second cross-class skills.
* Map keys are skill names.
*/
public final Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>> skillList;
/**
* Lists of this class's granted and selectable feats. First tuple member lists bonus feat
* # at each level. First inner tuple member is the granted feats, second member is selectable feats.
* Each list consists of the feats that are related to the list indexth level.
* The map keys are feat names.
*/
public final Tuple<List<Integer>, Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>> featList;
/**
* The alternate magic systems available to this class. Each list entry describes one
* magic system (though there will probably never be more than one per class).
* First member of the outer tuple is the (magic system name, magic system spell-equivalent name)
* pair. Second member is a map keyed to spell-equivalent level, with values being maps of
* spell-equivalent name to the spell entry.
*/
public final List<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>> magics;
/**
* Create a new class entry.
*
* @param name The name of this class.
* @param text The description of this class.
* @param iconPath The path of this class's icon.
* @param filePath The path of the html file that has been written for this class.
* @param isBase If <code>true</code>, this class is a base class.
* @param entryNum Index of the class in classes.2da.
* @param babSav The BAB and saves of this class.
* @param skillList The skills of this class.
* @param featList The feats of this class.
* @param magics The alternate magics of this class.
*/
public ClassEntry(String name, String text, String iconPath, String filePath,
int entryNum, boolean isBase, List<String[]> babSav,
Tuple<TreeMap<String, GenericEntry>, TreeMap<String, GenericEntry>> skillList,
Tuple<List<Integer>, Tuple<List<TreeMap<String, FeatEntry>>, List<TreeMap<String, FeatEntry>>>> featList,
List<Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>>> magics) {
super(name, text, iconPath, filePath, entryNum);
this.isBase = isBase;
this.babSav = babSav;
this.skillList = skillList;
this.featList = featList;
this.magics = magics;
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,278 @@
package prc.autodoc;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.stream.IntStream;
import java.util.HashMap;
import static prc.Main.spinner;
import static prc.Main.verbose;
/**
* This class forms an interface for accessing TLK files in the
* PRC automated manual generator.
*/
public class Data_TLK {
private final HashMap<Integer, String> mainData = new HashMap<Integer, String>();
private int highestEntry = 0;
/**
* Creates a new Data_TLK on the TLK file specified.
*
* @param filePath The path of the TLK file to be loaded
* @throws IllegalArgumentException <code>filePath</code> does not filePath a TLK file
* @throws TLKReadException reading the TLK file specified does not succeed
*/
public Data_TLK(String filePath) {
// Some paranoia checking for bad parameters
if (!filePath.toLowerCase().endsWith("tlk"))
throw new IllegalArgumentException("Non-tlk filename passed to Data_TLK: " + filePath);
File baseFile = new File(filePath);
if (!baseFile.exists())
throw new IllegalArgumentException("Nonexistent file passed to Data_TLK: " + filePath);
if (!baseFile.isFile())
throw new IllegalArgumentException("Nonfile passed to Data_TLK: " + filePath);
// Create a RandomAccessFile for reading the TLK. Read-only
MappedByteBuffer reader = null;
RandomAccessFile fileReader;
try {
fileReader = new RandomAccessFile(baseFile, "r");
reader = fileReader.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileReader.length());
} catch (IOException e) {
throw new TLKReadException("Cannot access TLK file: " + filePath, e);
}
byte[] bytes4 = new byte[4],
bytes8 = new byte[8];
// Drop the path from the filename
String fileName = baseFile.getName();
// Tell the user what we are doing
if (verbose) System.out.print("Reading TLK file: " + fileName + " ");
try {
// Check the header
reader.get(bytes4);
if (!new String(bytes4).equals("TLK "))
throw new TLKReadException("Wrong file type field in: " + fileName);
// Check the version
reader.get(bytes4);
if (!new String(bytes4).equals("V3.0"))
throw new TLKReadException("Wrong TLK version number in: " + fileName);
// Skip the language ID
reader.position(reader.position() + 4);
// Read the entrycount
int stringCount = readLittleEndianInt(reader, bytes4);
int stringOffset = readLittleEndianInt(reader, bytes4);
// Read the entry lengths
int[] stringLengths = readStringLengths(reader, stringCount);
// Read the strings themselves
readStrings(reader, stringLengths, stringOffset);
// Store the highest string for writing back later
highestEntry = stringLengths.length;
} catch (IOException e) {
throw new TLKReadException("IOException while reading TLK file: " + fileName, e);
} finally {
try {
fileReader.close();
} catch (IOException e) {
// No idea under what conditions closing a file could fail and not cause an Error to be thrown...
e.printStackTrace();
}
}
if (verbose) System.out.println("- Done");
}
/**
* Get the given TLK entry.
*
* @param strRef the number of the entry to get
* @return the entry string or "Bad StrRef" if the entry wasn't in the TLK
*/
public String getEntry(int strRef) {
if (strRef > 0x01000000) strRef -= 0x01000000;
String toReturn = mainData.get(strRef);
if (toReturn == null) toReturn = Main.badStrRef;
return toReturn;
}
/**
* Get the given TLK entry.
*
* @param strRef the number of the entry to get as a string
* @return the entry string or "Bad StrRef" if the entry wasn't in the TLK
* @throws NumberFormatException if <code>strRef</code> cannot be converted to an integer
*/
public String getEntry(String strRef) {
try {
return getEntry(Integer.parseInt(strRef));
} catch (NumberFormatException e) {
return Main.badStrRef;
}
}
/**
* Set the given TLK entry.
*
* @param strRef the number of the entry to set
* @param value the value of the entry to set
*/
public void setEntry(int strRef, String value) {
if (strRef > 0x01000000) strRef -= 0x01000000;
mainData.put(strRef, value);
if (strRef > highestEntry)
highestEntry = strRef;
}
/**
* Saves the tlk file to the given XML.
*
* @param name the name of the resulting file, without extensions
* @param path the path to the directory to save the file to
* @param allowOverWrite Whether to allow overwriting existing files
* @throws IOException if cannot overwrite, or the underlying IO throws one
*/
public void saveAsXML(String name, String path, boolean allowOverWrite) throws IOException {
if (path == null || path.equals(""))
path = "." + File.separator;
if (!path.endsWith(File.separator))
path += File.separator;
File file = new File(path + name + ".tlk.xml");
if (file.exists() && !allowOverWrite)
throw new IOException("File exists already: " + file.getAbsolutePath());
// Inform user
if (verbose) System.out.print("Saving tlk file: " + name + " ");
PrintWriter writer = new PrintWriter(file);
//write the header
writer.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
writer.println("<!DOCTYPE tlk SYSTEM \"tlk2xml.dtd\">");
writer.println("<tlk>");
//loop over each row and write it
for (int row = 0; row < highestEntry; row++) {
String data = mainData.get(row);
if (data != null) {
//replace with paired characters
data = data.replace("&", "&amp;"); //this must be before the others
data = data.replace("<", "&lt;");
data = data.replace(">", "&gt;");
writer.println(" <entry id=\"" + row + "\" lang=\"en\" sex=\"m\">" + data + "</entry>");
}
if (verbose) spinner.spin();
}
//write the footer
writer.println("</tlk>");
writer.flush();
writer.close();
if (verbose) System.out.println("- Done");
}
/**
* Reads the string lengths from this TLK's string data elements.
*
* @param reader RandomAccessFile read from
* @param stringCount number of strings in the TLK
* @return an array of integers containing the lengths of the strings in this TLK
* @throws IOException if there is an error while reading from <code>reader</code>
*/
private int[] readStringLengths(MappedByteBuffer reader, int stringCount) throws IOException {
int[] toReturn = new int[stringCount];
byte[] bytes4 = new byte[4];
int curOffset = 20; // The number of bytes in the TLK header section
for (int i = 0; i < stringCount; i++) {
// Skip everything up to the length
curOffset += 32;
reader.position(curOffset);
// Read the value
toReturn[i] = readLittleEndianInt(reader, bytes4);
// Skip to the end of the record
curOffset += 8;
if (verbose) spinner.spin();
}
return toReturn;
}
/**
* Reads the strings from the TLK into the hashmap.
*
* @param reader RandomAccessFile read from
* @param stringLengths an array of integers containing the lengths of the strings in this TLK
* @param curOffset the offset to start reading from in the file
* @throws IOException if there is an error while reading from <code>reader</code>
*/
private void readStrings(MappedByteBuffer reader, int[] stringLengths, int curOffset) throws IOException {
StringBuffer buffer = new StringBuffer(200);
reader.position(curOffset);
for (int i = 0; i < stringLengths.length; i++) {
if (stringLengths[i] > 0) {
// Read the specified number of bytes, convert them into chars
// and put them in the buffer
for (int j = 0; j < stringLengths[i]; j++)
buffer.append((char) (reader.get() & 0xff));
// Store the buffer contents
mainData.put(i, buffer.toString());
// Wipe the buffer for next round
buffer.delete(0, buffer.length());
}
if (verbose) spinner.spin();
}
}
/**
* Reads the next 4 bytes into the given array from the TLK and then
* writes them into an integer in inverse order.
*
* @param reader RandomAccessFile read from
* @param readArray array of bytes read to. For efficiency of not having to create a new array every time
* @return integer read
* @throws IOException if there is an error while reading from <code>reader</code>
*/
private int readLittleEndianInt(MappedByteBuffer reader, byte[] readArray) throws IOException {
int toReturn = 0;
reader.get(readArray);
for (int i = readArray.length - 1; i >= 0; i--) {
// What's missing here is the implicit promotion of readArray[i] to
// int. A byte is a signed element, and as such, has max value of 0x7f.
toReturn = (toReturn << 8) | readArray[i] & 0xff;
}
return toReturn;
}
/**
* The main method, as usual
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Data_TLK test = new Data_TLK(args[0]);
}
}

Binary file not shown.

View File

@@ -0,0 +1,37 @@
package prc.autodoc;
import java.util.List;
/**
* Data structure for a domain entry.
*/
public class DomainEntry extends GenericEntry {
/**
* This domain's domain power, if any
*/
public final FeatEntry grantedFeat;
/**
* The domain's spells. List of spell entry.
*/
public final List<SpellEntry> spells;
/**
* The constructor.
*
* @param name Name of the domain
* @param text Description of the domain
* @param iconPath Path of the domain's icon
* @param filePath Path where the html page for this domain will be written to
* @param entryNum domains.2da index
* @param grantedFeat Domain power feat
* @param spells The domain spells
*/
public DomainEntry(String name, String text, String iconPath, String filePath,
int entryNum, FeatEntry grantedFeat,
List<SpellEntry> spells) {
super(name, text, iconPath, filePath, entryNum);
this.grantedFeat = grantedFeat;
this.spells = spells;
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,66 @@
package prc.autodoc;
import java.io.FileOutputStream;
import java.io.PrintWriter;
/**
* A convenience class for printing errors to both log and System.err
*/
public class ErrorPrinter {
private PrintWriter writer;
private boolean isInit = false;
/**
* Initializes the ErrorPrinter.
* Attempts to open a log file by name of "errorlog" for writing. If this
* fails, aborts the program.
*/
private void init() {
try {
writer = new PrintWriter(new FileOutputStream("errorlog", false), true);
isInit = true;
} catch (Exception e) {
System.err.println("Error while creating error logger. Yes, it's ironic. Now debug");
e.printStackTrace();
System.exit(1);
}
}
/**
* Prints the given string to both stderr and errorlog.
*
* @param toPrint string to write
*/
public void print(String toPrint) {
if (!isInit)
init();
writer.print(toPrint);
System.err.print(toPrint);
}
/**
* Prints the given string to both stderr and errorlog. In addition, adds
* a line terminator to the end of the string.
*
* @param toPrint string to write
*/
public void println(String toPrint) {
if (!isInit)
init();
writer.println(toPrint);
System.err.println(toPrint);
}
/**
* Prints the given exception's stack trace to both stderr and errorlog.
*
* @param e exception to print
*/
public void printException(Exception e) {
if (!isInit)
init();
e.printStackTrace(System.out);
e.printStackTrace(writer);
}
}

Binary file not shown.

View File

@@ -0,0 +1,88 @@
package prc.autodoc;
import java.util.List;
import java.util.TreeMap;
/**
* Data structure for a feat or masterfeat entry.
*/
public class FeatEntry extends GenericEntry {
/**
* A list of masterfeat's children
*/
public TreeMap<String, FeatEntry> childFeats = new TreeMap<String, FeatEntry>();
/**
* A list of feats that have this feat as their prerequisite
*/
public TreeMap<String, FeatEntry> requiredForFeats = new TreeMap<String, FeatEntry>();
/**
* A list of this feat's AND prerequisites
*/
public TreeMap<String, FeatEntry> andRequirements = new TreeMap<String, FeatEntry>();
/**
* A list of this feat's OR prerequisites
*/
public TreeMap<String, FeatEntry> orRequirements = new TreeMap<String, FeatEntry>();
/**
* This feat's masterfeat, if any
*/
public FeatEntry master;
/**
* This feat's successor, if any
*/
public FeatEntry successor;
/**
* Whether this feat is epic, as defined by feat.2da column PreReqEpic. For masterfeats, if any of the children are epic
*/
public boolean isEpic;
/**
* Whether all of a masterfeat's children are epic. This is used to determine which menus a link to it will be printed
*/
public boolean allChildrenEpic;
/**
* Whether the feat is a class-specific feat, as defined by feat.2da column ALLCLASSESCANUSE. For masterfeats, if any of the children are class-specific
*/
public boolean isClassFeat;
/**
* Whether all of a masterfeat's children are class-specific. This is used to determine which menus a link to it will be printed
*/
public boolean allChildrenClassFeat;
/**
* Whether this feat has a successor
*/
public boolean isSuccessor;
/**
* The feat's subradials, if any. List of feat name, icon path tuples.
*/
public final List<Tuple<String, String>> subradials;
/**
* The constructor.
*
* @param name Name of the feat/masterfeat
* @param text Description of the feat
* @param iconPath Path of the feat's icon
* @param filePath Path where the html page for this feat will be written to
* @param entryNum feat.2da / masterfeats.2da index
* @param isEpic Whether the feat is an epic feat
* @param isClassFeat Whether the feat is a class-specific feat
* @param subradials List of this feat's subradials, if any
*/
public FeatEntry(String name, String text, String iconPath, String filePath,
int entryNum, boolean isEpic, boolean isClassFeat,
List<Tuple<String, String>> subradials) {
super(name, text, iconPath, filePath, entryNum);
this.isEpic = isEpic;
this.isClassFeat = isClassFeat;
this.subradials = subradials;
master = null;
successor = null;
isSuccessor = false;
allChildrenClassFeat = false;
allChildrenEpic = false;
}
}

Binary file not shown.

View File

@@ -0,0 +1,57 @@
package prc.autodoc;
/**
* Data structure for a skill /domain / race / class / whatever that doesn't need
* the extra fields used for feats & spells & skills
*/
public class GenericEntry implements Comparable<GenericEntry> {
/**
* The name of this entry.
*/
public final String name;
/**
* The contents of this entry.
*/
public final String text;
/**
* The path of the icon file attached to this entry.
*/
public final String iconPath;
/**
* The path of the html file that has been written for this entry.
*/
public final String filePath;
/**
* Index of the entry in whichever 2da defines it.
*/
public final int entryNum;
/**
* @param name Name of the entry
* @param text Content of the entry
* @param iconPath Path of this entry's icon
* @param filePath Path the generated file will be located in
* @param entryNum Number of the
*/
public GenericEntry(String name, String text, String iconPath, String filePath, int entryNum) {
this.name = name;
this.text = text;
this.iconPath = iconPath;
this.filePath = filePath;
this.entryNum = entryNum;
}
/**
* Comparable implementation. Uses the name fields for comparison.
*
* @param other GenericEntry to compare this one to
* @return @see java.lang.Comparable#compareTo
*/
public int compareTo(GenericEntry other) {
return name.compareTo(other.name);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,215 @@
package prc.autodoc;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static prc.Main.err_pr;
import static prc.autodoc.Main.*;
/**
* This class handles generating
*
* @author Ornedan
*/
public class Icons {
/**
* Visible from outside so that Main can wait on it.
*/
public static ExecutorService executor;
//private static Timer timer;
private static boolean init = false;
private static HashSet<String> existingIcons = null;
private static HashMap<String, File> rawIcons = null;
private static void init() {
/*try{
reader = Jimi.createTypedJimiReader("image/targa");
writer = Jimi.createTypedJimiWriter("image/png");
}catch(JimiException e){
err_pr.printException(e);
icons = false;
return;
}*/
init = true;
existingIcons = new HashSet<String>();
ImageIO.setUseCache(false);
//executor = new ThreadPoolExecutor(50, 1000, 60, TimeUnit.SECONDS, bq);
//timer = new Timer("Checker", true);
executor = Executors.newCachedThreadPool();
rawIcons = new HashMap<String, File>();
File[] tgas = new File("rawicons").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getName().toLowerCase().endsWith(".tga");
}
});
// Ensure the icons directory exists
if (tgas == null) {
err_pr.println("Error: Icons directory is missing!");
if (tolErr) return;
else System.exit(1);
}
for (File tga : tgas)
rawIcons.put(tga.getName().toLowerCase(), tga);
}
/**
* Builds an &lt;img&gt;-tag pointing at a png-converted version of the
* given icon.
* If such an icon does not exist, returns a blank string.
* The icon will be converted to PNG format if it has not yet been.
*
* @param iconName name of the icon to use
* @return &lt;img&gt;-tag pointing at the image or a blank string
*/
public static String buildIcon(String iconName) {
if (!icons) return "";
else if (!init) init();
if (iconName.equals("****"))
return "";
// Lowercase the name
iconName = iconName.toLowerCase();
// Build the html
String tag = iconTemplate.replace("~~~ImageURL~~~", "../../../images/" + iconName + ".png")
.replace("~~~ImageName~~~", iconName);
// Case of already existing and indexed image
if (existingIcons.contains(iconName))
return tag;
File png = new File(imagePath + iconName + ".png");
// See if the converted image exists already, but just hasn't been indexed yet
if (png.exists()) {
existingIcons.add(iconName);
return tag;
}
File tga = rawIcons.get(iconName + ".tga");
// There is no icon to convert
if (tga == null) {
err_pr.println("Error: Missing icon file: " + iconName + ".tga");
return "";
}
// Schedule the conversion for execution
Convert task = new Convert();
task.init(tga, png);
Future fut = executor.submit(task);
// Schedule timer task
/*
Check check = new Check();
check.init(fut, iconName);
timer.schedule(check, System.currentTimeMillis() + 30000);
*/
/*
try{
reader.setSource(new FileInputStream(new File("rawicons" + fileSeparator + iconName + ".tga")));
writer.setSource(reader.getImage());
writer.putImage(imagePath + iconName + ".png");
}catch(Exception e){
err_pr.printException(e);
throw new RuntimeException(e);
}
*/
// Assume eventual success and add the newly created image to the index
existingIcons.add(iconName);
return tag;
}
private static class Convert implements Runnable {
private File inFile, outFile;
/**
* Initialization method
*
* @param inFile input file, Targa format
* @param outFile output file, Portable Network Graphics format
*/
public void init(File inFile, File outFile) {
this.inFile = inFile;
this.outFile = outFile;
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
try {
if (!ImageIO.write(ImageIO.read(inFile), "png", outFile))
err_pr.println("Error: Failed to convert image " + inFile);
} catch (IOException e) {
err_pr.printException(e);
}
}
}
private static class Check extends TimerTask {
private Future toCheck;
private String iconName;
/**
* Initialization method
*
* @param toCheck future representing the image conversion to check on
* @param iconName name of the icon being converted by the task to check on
*/
public void init(Future toCheck, String iconName) {
this.toCheck = toCheck;
this.iconName = iconName;
}
/**
* @see java.util.TimerTask#run()
*/
public void run() {
if (!toCheck.isDone()) {
toCheck.cancel(true);
err_pr.println("Error: Failed icon conversion on file " + iconName + ".tga");
}
//executor.purge();
}
}
/*
public static void main(String[] args) throws Exception{
System.out.println("Yar!");
icons = true;
iconTemplate = readTemplate("templates" + fileSeparator + "english" + fileSeparator + "icon.html");
System.out.println(buildImg("test"));
ImageIO.setUseCache(false);
Iterator<ImageReader> it = ImageIO.getImageReadersByMIMEType("image/targa");
while(it.hasNext())
System.out.println(it.next().getFormatName());
java.awt.image.BufferedImage buf = ImageIO.read(new File("isk_x1app.tga"));
System.out.println("Tar!");
ImageIO.write(buf, "png", new File("test.png"));
System.out.println("Far!");
}
*/
}

Binary file not shown.

View File

@@ -0,0 +1,984 @@
package prc.autodoc;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static prc.Main.*;
import static prc.autodoc.EntryGeneration.*;
import static prc.autodoc.MenuGeneration.*;
import static prc.autodoc.PageGeneration.*;
/**
* The main purpose of this autodocumenter is to create parts of the manual for
* the PRC pack from 2da and TLK files. As a side effect of doing so, it finds
* many errors present in the 2das.
*/
public class Main {
/**
* A small data structure class that gives access to both normal and custom
* TLK with the same method
*/
public static class TLKStore {
private final Data_TLK normal;
private final Data_TLK custom;
/**
* Creates a new TLKStore around the given two filenames. Equivalent to
* TLKStore(normalName, customName, "tlk").
*
* @param normalName dialog.tlk or equivalent for the given language
* @param customName prc_consortium.tlk or equivalent for the given languag
* @throws TLKReadException if there are any problems reading either TLK
*/
public TLKStore(String normalName, String customName) {
this.normal = new Data_TLK("tlk" + fileSeparator + normalName);
this.custom = new Data_TLK("tlk" + fileSeparator + customName);
}
/**
* Creates a new TLKStore around the given two filenames.
*
* @param normalName dialog.tlk or equivalent for the given language
* @param customName prc_consortium.tlk or equivalent for the given languag
* @param tlkDir Directory containing the two .tlk files
* @throws TLKReadException if there are any problems reading either TLK
*/
public TLKStore(String normalName, String customName, String tlkDir) {
this.normal = new Data_TLK(tlkDir + fileSeparator + normalName);
this.custom = new Data_TLK(tlkDir + fileSeparator + customName);
}
/**
* Returns the TLK entry for the given StrRef. If there is nothing
* at the location, returns Bad StrRef. Automatically picks between
* normal and custom TLKs.
*
* @param num the line number in TLK
* @return the contents of the given TLK slot, or Bad StrRef
*/
public String get(int num) {
return num < 0x01000000 ? normal.getEntry(num) : custom.getEntry(num);
}
/**
* See above, except that this one automatically parses the string for
* the number.
*
* @param num the line number in TLK as string
* @return as above, except it returns Bad StrRef in case parsing failed
*/
public String get(String num) {
try {
return get(Integer.parseInt(num));
} catch (NumberFormatException e) {
return Main.badStrRef;
}
}
}
/**
* Another data structure class. Stores 2das and handles loading them.
*/
public static class TwoDAStore {
private static class Loader implements Runnable {
private final String pathToLoad;
private final List<Data_2da> list;
private final CountDownLatch latch;
/**
* Creates a new Loader to load the given 2da file
*
* @param pathToLoad path of the 2da to load
* @param list list to store the loaded data into
* @param latch latch to countdown on once loading is complete
*/
public Loader(String pathToLoad, List<Data_2da> list, CountDownLatch latch) {
this.pathToLoad = pathToLoad;
this.list = list;
this.latch = latch;
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
try {
Data_2da data = Data_2da.load2da(pathToLoad, true);
list.add(data);
latch.countDown();
} catch (Exception e) {
err_pr.println("Error: Failure while reading main 2das. Exception data:\n");
err_pr.printException(e);
System.exit(1);
}
}
}
private final HashMap<String, Data_2da> data = new HashMap<String, Data_2da>();
private final String twoDAPath;
/**
* Creates a new TwoDAStore, without preloading anything.
*
* @param twoDAPath Path of the directory containing 2da files.
*/
public TwoDAStore(String twoDAPath) {
this.twoDAPath = twoDAPath;
}
/**
* Generates a new TwoDAStore with all the main 2das preread in.
* On a read failure, kills program execution, since there's nothing
* that could be done anyway.
*/
public TwoDAStore() {
this("2da");
//long start = System.currentTimeMillis();
if (verbose) System.out.print("Loading main 2da files ");
boolean oldVerbose = verbose;
verbose = false;
CountDownLatch latch = new CountDownLatch(7);
List<Data_2da> list = Collections.synchronizedList(new ArrayList<Data_2da>());
// Read the main 2das
new Thread(new Loader("2da" + fileSeparator + "classes.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "domains.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "feat.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "masterfeats.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "racialtypes.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "skills.2da", list, latch)).start();
new Thread(new Loader("2da" + fileSeparator + "spells.2da", list, latch)).start();
try {
latch.await();
} catch (InterruptedException e) {
err_pr.println("Error: Interrupted while reading main 2das. Exception data:\n");
err_pr.printException(e);
System.exit(1);
}
for (Data_2da entry : list)
data.put(entry.getName(), entry);
verbose = oldVerbose;
if (verbose) System.out.println("- Done");
/*
try{
data.put("classes", new Data_2da("2da" + fileSeparator + "classes.2da"));
data.put("domains", new Data_2da("2da" + fileSeparator + "domains.2da"));
data.put("feat", new Data_2da("2da" + fileSeparator + "feat.2da"));
data.put("masterfeats", new Data_2da("2da" + fileSeparator + "masterfeats.2da"));
data.put("racialtypes", new Data_2da("2da" + fileSeparator + "racialtypes.2da"));
data.put("skills", new Data_2da("2da" + fileSeparator + "skills.2da"));
data.put("spells", new Data_2da("2da" + fileSeparator + "spells.2da"));
}catch(Exception e){
err_pr.println("Error: Failure while reading main 2das. Exception data:\n");
err_pr.printException(e);
System.exit(1);
}
*/
//System.out.println("Time taken: " + (System.currentTimeMillis() - start));
}
/**
* Gets a Data_2da structure wrapping the given 2da. If it hasn't been loaded
* yet, the loading is done now.
*
* @param name name of the 2da to get. Without the file end ".2da".
* @return a Data_2da structure
* @throws TwoDAReadException if any errors are encountered while reading
*/
public Data_2da get(String name) {
if (data.containsKey(name))
return data.get(name);
else {
Data_2da temp = null;
try {
temp = Data_2da.load2da(twoDAPath + fileSeparator + name + ".2da", true);
} catch (IllegalArgumentException e) {
throw new TwoDAReadException("Problem with filename when trying to read from 2da:\n" + e);
}
data.put(name, temp);
return temp;
}
}
}
/**
* A class for handling the settings file.
*/
public static class Settings {
/* Some pattern matchers for use when parsing the settings file */
private final Matcher mainMatch = Pattern.compile("\\S+:").matcher("");
private final Matcher paraMatch = Pattern.compile("\"[^\"]+\"").matcher("");
private final Matcher langMatch = Pattern.compile("\\w+=\"[^\"]+\"").matcher("");
/* An enumeration of the possible setting types */
private enum Modes {
/**
* The parser is currently working on lines specifying languages used.
*/
LANGUAGE,
/**
* The parser is currently working on lines containing string patterns that are
* used in differentiating between entries in spells.2da.
*/
SIGNATURE,
/**
* The parser is currently working on lines listing spells.2da entries that contain
* a significantly modified BW spell.
*/
MODIFIED_SPELL
}
/* Settings data read in */
/**
* The settings for languages. An ArrayList of String[] containing setting for a specific language
*/
public ArrayList<String[]> languages = new ArrayList<String[]>();
/**
* An ArrayList of Integers. Indices to spells.2da of standard spells modified by the PRC
*/
public ArrayList<Integer> modifiedSpells = new ArrayList<Integer>();
/**
* A set of script name prefixes used to find epic spell entries in spells.2da
*/
public String[] epicspellSignatures = null;
/*/** A set of script name prefixes used to find psionic power entries in spells.2da *
public String[] psionicpowerSignatures = null;*/
/**
* Read the settings file in and store the data for later access.
* Terminates execution on any errors.
*/
public Settings() {
try {
// The settings file should be present in the directory this is run from
Scanner reader = new Scanner(new File("settings"));
String check;
Modes mode = null;
while (reader.hasNextLine()) {
check = reader.nextLine();
// Skip comments and blank lines
if (check.startsWith("#") || check.trim().equals("")) continue;
// Check if a new rule is starting
mainMatch.reset(check);
if (mainMatch.find()) {
if (mainMatch.group().equals("language:")) mode = Modes.LANGUAGE;
else if (mainMatch.group().equals("signature:")) mode = Modes.SIGNATURE;
else if (mainMatch.group().equals("modified_spell:")) mode = Modes.MODIFIED_SPELL;
else {
throw new Exception("Unrecognized setting detected");
}
continue;
}
// Take action based on current mode
if (mode == Modes.LANGUAGE) {
String[] temp = new String[LANGDATA_NUMENTRIES];
String result;
langMatch.reset(check);
// parse the language entry
for (int i = 0; i < LANGDATA_NUMENTRIES; i++) {
if (!langMatch.find())
throw new Exception("Missing language parameter");
result = langMatch.group();
if (result.startsWith("name=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_LANGNAME] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("base=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_BASETLK] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("prc=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_PRCTLK] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("feats=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_FEATSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("allfeats=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_ALLFEATSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("epicfeats=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_EPICFEATSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("allepicfeats=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_ALLEPICFEATSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("baseclasses=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_BASECLASSESTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("prestigeclasses=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_PRESTIGECLASSESTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("spells=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_SPELLSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("epicspells=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_EPICSPELLSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("psipowers=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_PSIONICPOWERSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("modspells=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_MODIFIEDSPELLSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("skills=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_SKILLSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("domains=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_DOMAINSTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("races=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_RACESTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("spellbook=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_SPELLBOOKTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("powers=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_POWERTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("truenameutterances=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_TRUENAMEUTTERANCETXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("invocations=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_INVOCATIONTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("maneuvers=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_MANEUVERTXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else if (result.startsWith("utterances=")) {
paraMatch.reset(result);
paraMatch.find();
temp[LANGDATA_UTTERANCETXT] = paraMatch.group().substring(1, paraMatch.group().length() - 1);
} else
throw new Exception("Unknown language parameter encountered\n" + check);
}
languages.add(temp);
}
// Parse the spell script name signatures
if (mode == Modes.SIGNATURE) {
String[] temp = check.trim().split("=");
if (temp[0].equals("epicspell")) {
epicspellSignatures = temp[1].replace("\"", "").split("\\|");
}/* Not needed anymore
else if(temp[0].equals("psionicpower")){
psionicpowerSignatures = temp[1].replace("\"", "").split("\\|");
}*/ else
throw new Exception("Unknown signature parameter encountered:\n" + check);
}
// Parse the spell modified spell indices
if (mode == Modes.MODIFIED_SPELL) {
modifiedSpells.add(Integer.parseInt(check.trim()));
}
}
} catch (Exception e) {
err_pr.println("Error: Failed to read settings file:\n" + e + "\nAborting");
System.exit(1);
}
}
}
/**
* A small enumeration for use in spell printing methods
*/
public enum SpellType {
/**
* The spell is not a real spell or psionic power, instead specifies some feat's spellscript.
*/
NONE,
/**
* The spell is a normal spell.
*/
NORMAL,
/**
* The spell is an epic spell.
*/
EPIC,
/**
* The spell is a psionic power.
*/
PSIONIC,
/**
* The spell is a truename utterance.
*/
UTTERANCE,
/**
* The spell is an invocation.
*/
INVOCATION,
/**
* The spell is a maneuver.
*/
MANEUVER
}
/**
* A switche determinining how errors are handled
*/
public static boolean tolErr = true;
/**
* A boolean determining whether to print icons for the pages or not
*/
public static boolean icons = false;
/**
* A constant signifying Bad StrRef
*/
public static final String badStrRef = "Bad StrRef";
/**
* The container object for general configuration data read from file
*/
public static Settings settings;// = new Settings();
/**
* The file separator, given it's own constant for ease of use
*/
public static final String fileSeparator = System.getProperty("file.separator");
/**
* Array of the settings for currently used language. Index with the LANGDATA_ constants
*/
public static String[] curLanguageData = null;
/**
* Size of the curLanguageData array
*/
public static final int LANGDATA_NUMENTRIES = 22;
/**
* curLanguageData index of the language name
*/
public static final int LANGDATA_LANGNAME = 0;
/**
* curLanguageData index of the name of the dialog.tlk equivalent for this language
*/
public static final int LANGDATA_BASETLK = 1;
/**
* curLanguageData index of the name of the prc_consortium.tlk equivalent for this language
*/
public static final int LANGDATA_PRCTLK = 2;
/**
* curLanguageData index of the name of the "All Feats" string equivalent for this language
*/
public static final int LANGDATA_ALLFEATSTXT = 3;
/**
* curLanguageData index of the name of the "All Epic Feats" string equivalent for this language
*/
public static final int LANGDATA_ALLEPICFEATSTXT = 4;
/**
* curLanguageData index of the name of the "Feats" string equivalent for this language
*/
public static final int LANGDATA_FEATSTXT = 5;
/**
* curLanguageData index of the name of the "Epic Feats" string equivalent for this language
*/
public static final int LANGDATA_EPICFEATSTXT = 6;
/**
* curLanguageData index of the name of the "Base Classes" string equivalent for this language
*/
public static final int LANGDATA_BASECLASSESTXT = 7;
/**
* curLanguageData index of the name of the "Prestige Classes" string equivalent for this language
*/
public static final int LANGDATA_PRESTIGECLASSESTXT = 8;
/**
* curLanguageData index of the name of the "Spells" string equivalent for this language
*/
public static final int LANGDATA_SPELLSTXT = 9;
/**
* curLanguageData index of the name of the "Epic Spells" string equivalent for this language
*/
public static final int LANGDATA_EPICSPELLSTXT = 10;
/**
* curLanguageData index of the name of the "Psionic Powers" string equivalent for this language
*/
public static final int LANGDATA_PSIONICPOWERSTXT = 11;
/**
* curLanguageData index of the name of the "Modified Spells" string equivalent for this language
*/
public static final int LANGDATA_MODIFIEDSPELLSTXT = 12;
/**
* curLanguageData index of the name of the "Domains" string equivalent for this language
*/
public static final int LANGDATA_DOMAINSTXT = 13;
/**
* curLanguageData index of the name of the "Skills" string equivalent for this language
*/
public static final int LANGDATA_SKILLSTXT = 14;
/**
* curLanguageData index of the name of the "Races" string equivalent for this language
*/
public static final int LANGDATA_RACESTXT = 15;
/**
* curLanguageData index of the name of the "Spellbook" string equivalent for this language
*/
public static final int LANGDATA_SPELLBOOKTXT = 16;
/**
* curLanguageData index of the name of the "Powers" string equivalent for this language
*/
public static final int LANGDATA_POWERTXT = 17;
/**
* curLanguageData index of the name of the "Truename Utterances" string equivalent for this language
*/
public static final int LANGDATA_TRUENAMEUTTERANCETXT = 18;
/**
* curLanguageData index of the name of the "Invocations" string equivalent for this language
*/
public static final int LANGDATA_INVOCATIONTXT = 19;
/**
* curLanguageData index of the name of the "Maneuvers" string equivalent for this language
*/
public static final int LANGDATA_MANEUVERTXT = 20;
/**
* curLanguageData index of the name of the "Utterances" string equivalent for this language
*/
public static final int LANGDATA_UTTERANCETXT = 21;
/**
* Current language name
*/
public static String curLanguage = null;
/**
* The base path. <code>"manual" + fileSeparator + curLanguage + fileSeparator</code>
*/
public static String mainPath = null;
/**
* The path to content directory. <code>mainPath + "content" + fileSeparator</code>
*/
public static String contentPath = null;
/**
* The path to menu directory. <code>mainPath + "mainPath" + fileSeparator</code>
*/
public static String menuPath = null;
/**
* The path to the image directory. <code>"manual" + fileSeparator + "images" + fileSeparator</code>
*/
public static String imagePath = "manual" + fileSeparator + "images" + fileSeparator;
/**
* Data structures for accessing TLKs
*/
public static TwoDAStore twoDA;
/**
* Data structures for accessing TLKs
*/
public static TLKStore tlk;
/**
* The template files
*/
public static String babAndSavthrTableHeaderTemplate = null,
classTemplate = null,
classTablesEntryTemplate = null,
domainTemplate = null,
featTemplate = null,
mFeatTemplate = null,
menuTemplate = null,
menuItemTemplate = null,
prereqANDFeatHeaderTemplate = null,
prereqORFeatHeaderTemplate = null,
raceTemplate = null,
spellTemplate = null,
skillTableHeaderTemplate = null,
skillTemplate = null,
successorFeatHeaderTemplate = null,
iconTemplate = null,
listEntrySetTemplate = null,
listEntryTemplate = null,
alphaSortedListTemplate = null,
requiredForFeatHeaderTemplate = null,
pageLinkTemplate = null,
featMenuTemplate = null,
spellSubradialListTemplate = null,
spellSubradialListEntryTemplate = null,
classFeatTableTemplate = null,
classFeatTableEntryTemplate = null,
classMagicTableTemplate = null,
classMagicTableEntryTemplate = null,
craftTemplate = null;
/* Data structures to store generated entry data in */
public static HashMap<Integer, SpellEntry> spells;
public static HashMap<Integer, FeatEntry> masterFeats,
feats;
public static HashMap<Integer, ClassEntry> classes;
public static HashMap<Integer, DomainEntry> domains;
public static HashMap<Integer, RaceEntry> races;
public static HashMap<Integer, GenericEntry> skills;
public static HashMap<Integer, GenericEntry> craft_armour;
public static HashMap<Integer, GenericEntry> craft_weapon;
public static HashMap<Integer, GenericEntry> craft_ring;
public static HashMap<Integer, GenericEntry> craft_wondrous;
/**
* Map of psionic power names to the indexes of the spells.2da entries chosen to represent the power in question
*/
public static HashMap<String, Integer> psiPowMap;
/**
* Map of truenaming utterance names to the spells.2da indexes that contain utterance feat-linked entries
*/
public static HashMap<String, Integer> utterMap;
/**
* Map of invocations to spells.2da
*/
public static HashMap<String, Integer> invMap;
/**
* Map of maneuvers to spells.2da
*/
public static HashMap<String, Integer> maneuverMap;
/**
* Ye olde maine methode
*
* @param args
*/
public static void main(String[] args) {
/* Argument parsing */
for (String opt : args) {
if (opt.equals("--help"))
readMe();
if (opt.startsWith("-")) {
if (opt.contains("a"))
tolErr = true;
if (opt.contains("q")) {
verbose = false;
spinner.disable();
}
if (opt.contains("i"))
icons = true;
if (opt.contains("s"))
spinner.disable();
if (opt.contains("?"))
readMe();
}
}
// Load the settings
settings = new Settings();
// Initialize the 2da container data structure
twoDA = new TwoDAStore();
// Print the manual files for each language specified
for (int i = 0; i < settings.languages.size(); i++) {
// Set language, path and load TLKs
curLanguageData = settings.languages.get(i);
curLanguage = curLanguageData[LANGDATA_LANGNAME];
mainPath = "manual" + fileSeparator + curLanguage + fileSeparator;
contentPath = mainPath + "content" + fileSeparator;
menuPath = mainPath + "menus" + fileSeparator;
// If we fail on a language, skip to next one
try {
tlk = new TLKStore(curLanguageData[LANGDATA_BASETLK], curLanguageData[LANGDATA_PRCTLK]);
} catch (TLKReadException e) {
err_pr.println("Error: Failure while reading TLKs for language: " + curLanguage + ":\n" + e);
continue;
}
// Skip to next if there is any problem with directories or templates
if (!(readTemplates() && buildDirectories())) continue;
// Do the actual work
createPages();
createMenus();
}
// Wait for the image conversion to finish before exiting main
if (Icons.executor != null) {
Icons.executor.shutdown();
try {
Icons.executor.awaitTermination(120, TimeUnit.SECONDS);
} catch (InterruptedException e) {
err_pr.println("Error: Interrupted while waiting for image conversion to finish");
} finally {
System.exit(0);
}
}
}
/**
* Prints the use instructions for this program and kills execution.
*/
private static void readMe() {
System.out.println("Usage:\n" +
" java prc/autodoc/Main [--help][-aiqs?]\n" +
"\n" +
"-a forces aborting printing on errors\n" +
"-i adds icons to pages\n" +
"-q quiet mode. Does not print any progress info, only failure messages\n" +
"-s disable the spinner. Useful when logging to file\n" +
"\n" +
"--help prints this info you are reading\n" +
"-? see --help\n"
);
System.exit(0);
}
/**
* Reads all the template files for the current language.
*
* @return <code>true</code> if all the reads succeeded, <code>false</code> otherwise
*/
private static boolean readTemplates() {
String templatePath = "templates" + fileSeparator + curLanguage + fileSeparator;
try {
babAndSavthrTableHeaderTemplate = readTemplate(templatePath + "babNsavthrtableheader.html");
classTablesEntryTemplate = readTemplate(templatePath + "classtablesentry.html");
classTemplate = readTemplate(templatePath + "class.html");
domainTemplate = readTemplate(templatePath + "domain.html");
featTemplate = readTemplate(templatePath + "feat.html");
mFeatTemplate = readTemplate(templatePath + "masterfeat.html");
menuTemplate = readTemplate(templatePath + "menu.html");
menuItemTemplate = readTemplate(templatePath + "menuitem.html");
prereqANDFeatHeaderTemplate = readTemplate(templatePath + "prerequisiteandfeatheader.html");
prereqORFeatHeaderTemplate = readTemplate(templatePath + "prerequisiteorfeatheader.html");
raceTemplate = readTemplate(templatePath + "race.html");
spellTemplate = readTemplate(templatePath + "spell.html");
skillTableHeaderTemplate = readTemplate(templatePath + "skilltableheader.html");
skillTemplate = readTemplate(templatePath + "skill.html");
successorFeatHeaderTemplate = readTemplate(templatePath + "successorfeatheader.html");
iconTemplate = readTemplate(templatePath + "icon.html");
listEntrySetTemplate = readTemplate(templatePath + "listpageentryset.html");
listEntryTemplate = readTemplate(templatePath + "listpageentry.html");
alphaSortedListTemplate = readTemplate(templatePath + "alphasorted_listpage.html");
requiredForFeatHeaderTemplate = readTemplate(templatePath + "reqforfeatheader.html");
pageLinkTemplate = readTemplate(templatePath + "pagelink.html");
featMenuTemplate = readTemplate(templatePath + "featmenu.html");
spellSubradialListTemplate = readTemplate(templatePath + "spellsubradials.html");
spellSubradialListEntryTemplate = readTemplate(templatePath + "spellsubradialsentry.html");
classFeatTableTemplate = readTemplate(templatePath + "classfeattable.html");
classFeatTableEntryTemplate = readTemplate(templatePath + "classfeattableentry.html");
classMagicTableTemplate = readTemplate(templatePath + "classmagictable.html");
classMagicTableEntryTemplate = readTemplate(templatePath + "classmagictableentry.html");
craftTemplate = readTemplate(templatePath + "craftprop.html");
} catch (IOException e) {
return false;
}
return true;
}
/**
* Reads the template file given as parameter and returns a string with it's contents
* Kills execution if any operations fail.
*
* @param filePath string representing the path of the template file
* @return the contents of the template file as a string
* @throws IOException if the reading fails
*/
private static String readTemplate(String filePath) throws IOException {
try {
Scanner reader = new Scanner(new File(filePath));
StringBuffer temp = new StringBuffer();
while (reader.hasNextLine()) temp.append(reader.nextLine() + "\n");
return temp.toString();
} catch (Exception e) {
err_pr.println("Error: Failed to read template file:\n" + e);
throw new IOException();
}
}
/**
* Creates the directory structure for the current language
* being processed.
*
* @return <code>true</code> if all directories are successfully created,
* <code>false</code> otherwise
*/
private static boolean buildDirectories() {
String dirPath = mainPath + "content";
boolean toReturn = buildDir(dirPath);
dirPath += fileSeparator;
toReturn = toReturn
&& buildDir(dirPath + "base_classes")
&& buildDir(dirPath + "class_epic_feats")
&& buildDir(dirPath + "class_feats")
&& buildDir(dirPath + "domains")
&& buildDir(dirPath + "epic_feats")
&& buildDir(dirPath + "epic_spells")
&& buildDir(dirPath + "feats")
&& buildDir(dirPath + "itemcrafting")
&& buildDir(dirPath + "master_feats")
&& buildDir(dirPath + "prestige_classes")
&& buildDir(dirPath + "psionic_powers")
&& buildDir(dirPath + "races")
&& buildDir(dirPath + "skills")
&& buildDir(dirPath + "spells")
&& buildDir(dirPath + "utterances")
&& buildDir(dirPath + "invocations")
&& buildDir(dirPath + "maneuvers")
&& buildDir(mainPath + "menus");
System.gc();
return toReturn;
}
/**
* Does the actual work of building the directories
*
* @param path the target directory to create
* @return <code>true</code> if the directory was already present or was successfully created,
* <code>false</code> otherwise
*/
private static boolean buildDir(String path) {
File builder = new File(path);
if (!builder.exists()) {
if (!builder.mkdirs()) {
err_pr.println("Error: Failure creating directory:\n" + builder.getPath());
return false;
}
} else {
if (!builder.isDirectory()) {
err_pr.println(builder.getPath() + " already exists as a file!");
return false;
}
}
return true;
}
/**
* Replaces each line break in the given TLK entry with
* a line break followed by <code>&lt;br /&gt;</code>.
*
* @param toHTML tlk entry to convert
* @return the modified string
*/
public static String htmlizeTLK(String toHTML) {
return toHTML.replaceAll("\n", "\n<br />");
}
/**
* Creates a new file at the given <code>path</code>, erasing previous file if present.
* Prints the given <code>content</code> string into the file.
*
* @param path the path of the file to be created
* @param content the string to be printed into the file
* @throws PageGenerationException if one of the file operations fails
*/
public static void printPage(String path, String content) {
try {
File target = new File(path);
// Clean up old version if necessary
if (target.exists()) {
if (verbose) System.out.println("Deleting previous version of " + path);
target.delete();
}
target.createNewFile();
// Creater the writer and print
FileWriter writer = new FileWriter(target, false);
writer.write(content);
// Clean up
writer.flush();
writer.close();
} catch (IOException e) {
throw new PageGenerationException("IOException when printing " + path, e);
}
}
/**
* Page creation. Calls all the specific functions for different page types
*/
private static void createPages() {
/* First, do the pages that do not require linking to other pages */
doSkills();
doCrafting();
listPsionicPowers();
listTruenameUtterances();
listInvocations();
listManeuvers();
doSpells();
/* Then, build the feats */
preliMasterFeats();
preliFeats();
linkFeats();
/* Last, domains, races and classes, which all link to the previous */
doDomains();
doRaces();
doClasses();
/* Then, print all of it */
printSkills();
printSpells();
printFeats();
printDomains();
printRaces();
printClasses();
printCrafting();
}
/**
* Menu creation. Calls the specific functions for different menu types
*/
private static void createMenus() {
/* First, the types that do not need any extra data beyond name & path
* and use GenericEntry
*/
doGenericMenu(skills, curLanguageData[LANGDATA_SKILLSTXT], "manual_menus_skills.html");
doGenericMenu(domains, curLanguageData[LANGDATA_DOMAINSTXT], "manual_menus_domains.html");
doGenericMenu(races, curLanguageData[LANGDATA_RACESTXT], "manual_menus_races.html");
doGenericMenu(craft_armour, curLanguageData[LANGDATA_SKILLSTXT], "manual_menus_craft_armour.html");
doGenericMenu(craft_weapon, curLanguageData[LANGDATA_SKILLSTXT], "manual_menus_craft_weapon.html");
doGenericMenu(craft_ring, curLanguageData[LANGDATA_SKILLSTXT], "manual_menus_craft_ring.html");
doGenericMenu(craft_wondrous, curLanguageData[LANGDATA_SKILLSTXT], "manual_menus_craft_wondrous.html");
/* Then the more specialised data where it needs to be split over several
* menu pages
*/
doSpellMenus();
doFeatMenus();
doClassMenus();
}
}

View File

@@ -0,0 +1,263 @@
package prc.autodoc;
//import java.io.*;
import java.util.HashMap;
import java.util.TreeMap;
import static prc.Main.verbose;
import static prc.autodoc.Main.*;
import static prc.autodoc.Main.SpellType.*;
public final class MenuGeneration {
private MenuGeneration() {/* No instances */}
/**
* Sorts any of the pages for which GenericEntry is enough into alphabetic order
* using a TreeMap, and prints a menu page out of the results.
*/
public static void doGenericMenu(HashMap<Integer, ? extends GenericEntry> entries, String menuName, String menuFileName) {
TreeMap<String, String> links = new TreeMap<String, String>();
StringBuffer toPrint = new StringBuffer();
if (verbose) System.out.println("Printing menu for " + menuName);
for (GenericEntry entry : entries.values()) {
links.put(entry.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
entry.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", entry.name));
}
while (links.size() > 0) {
toPrint.append(links.remove(links.firstKey()));
}
printPage(menuPath + menuFileName, menuTemplate.replaceAll("~~~menuName~~~", menuName)
.replaceAll("~~~menuEntries~~~", toPrint.toString()));
}
/**
* Sorts the spells into alphabetic order using a TreeMap, and prints a menu
* page out of the results. Normal, epic and psionics get their own menus
*/
public static void doSpellMenus() {
TreeMap<String, String> normalSpellLinks = new TreeMap<String, String>(),
epicSpellLinks = new TreeMap<String, String>(),
psionicPowerLinks = new TreeMap<String, String>(),
utteranceLinks = new TreeMap<String, String>(),
invocationLinks = new TreeMap<String, String>(),
maneuverLinks = new TreeMap<String, String>(),
modSpellLinks = new TreeMap<String, String>();
StringBuffer normalPrint = new StringBuffer(),
epicPrint = new StringBuffer(),
psionicPrint = new StringBuffer(),
utterancePrint = new StringBuffer(),
invocationPrint = new StringBuffer(),
maneuverPrint = new StringBuffer(),
modSpellPrint = new StringBuffer();
String temp = null;
if (verbose) System.out.println("Printing spell menus");
for (SpellEntry spell : spells.values()) {
switch (spell.type) {
case NORMAL:
normalSpellLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
break;
case EPIC:
temp = spell.name.startsWith("Epic Spell: ") ? spell.name.substring(12) : spell.name;
epicSpellLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", temp));
break;
case PSIONIC:
psionicPowerLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
break;
case UTTERANCE:
utteranceLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
break;
case INVOCATION:
invocationLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
break;
case MANEUVER:
maneuverLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
break;
default:
throw new AssertionError("Unhandled spelltype: " + spell.type);
}
if (settings.modifiedSpells.contains(spell.entryNum))
modSpellLinks.put(spell.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
spell.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", spell.name));
}
while (normalSpellLinks.size() > 0)
normalPrint.append(normalSpellLinks.remove(normalSpellLinks.firstKey()));
while (epicSpellLinks.size() > 0)
epicPrint.append(epicSpellLinks.remove(epicSpellLinks.firstKey()));
while (psionicPowerLinks.size() > 0)
psionicPrint.append(psionicPowerLinks.remove(psionicPowerLinks.firstKey()));
while (utteranceLinks.size() > 0)
utterancePrint.append(utteranceLinks.remove(utteranceLinks.firstKey()));
while (invocationLinks.size() > 0)
invocationPrint.append(invocationLinks.remove(invocationLinks.firstKey()));
while (maneuverLinks.size() > 0)
maneuverPrint.append(maneuverLinks.remove(maneuverLinks.firstKey()));
while (modSpellLinks.size() > 0)
modSpellPrint.append(modSpellLinks.remove(modSpellLinks.firstKey()));
printPage(menuPath + "manual_menus_spells.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_SPELLSTXT])
.replaceAll("~~~menuEntries~~~", normalPrint.toString()));
printPage(menuPath + "manual_menus_epic_spells.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_EPICSPELLSTXT])
.replaceAll("~~~menuEntries~~~", epicPrint.toString()));
printPage(menuPath + "manual_menus_psionic_powers.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_PSIONICPOWERSTXT])
.replaceAll("~~~menuEntries~~~", psionicPrint.toString()));
printPage(menuPath + "manual_menus_truename_utterances.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_TRUENAMEUTTERANCETXT])
.replaceAll("~~~menuEntries~~~", utterancePrint.toString()));
printPage(menuPath + "manual_menus_invocations.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_INVOCATIONTXT])
.replaceAll("~~~menuEntries~~~", invocationPrint.toString()));
printPage(menuPath + "manual_menus_maneuvers.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_MANEUVERTXT])
.replaceAll("~~~menuEntries~~~", maneuverPrint.toString()));
printPage(menuPath + "manual_menus_modified_spells.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_MODIFIEDSPELLSTXT])
.replaceAll("~~~menuEntries~~~", modSpellPrint.toString()));
}
/**
* Sorts the feats into alphabetic order using a TreeMap, and prints a menu
* page out of the results. Normal and epic feats get their own menus and class feats
* are skipped.
*/
public static void doFeatMenus() {
TreeMap<String, String> normalFeatLinks = new TreeMap<String, String>(),
normalMasterfeatLinks = new TreeMap<String, String>(),
epicFeatLinks = new TreeMap<String, String>(),
epicMasterfeatLinks = new TreeMap<String, String>();
StringBuffer normalList = new StringBuffer(),
normalMasterList = new StringBuffer(),
epicList = new StringBuffer(),
epicMasterList = new StringBuffer();
String temp = null;
String normalMenu = featMenuTemplate,
epicMenu = featMenuTemplate;
if (verbose) System.out.println("Printing feat menus");
// Print names
normalMenu = normalMenu.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_FEATSTXT]);
epicMenu = epicMenu.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_EPICFEATSTXT]);
// Parse through feats
for (FeatEntry feat : feats.values()) {
// Skip class feats and feats with masterfeat or a predecessor
if (feat.isClassFeat || feat.isSuccessor || feat.master != null) continue;
if (!feat.isEpic)
normalFeatLinks.put(feat.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
feat.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", feat.name));
else
epicFeatLinks.put(feat.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
feat.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", feat.name));
}
// Transfer the feat link lists into text form
while (normalFeatLinks.size() > 0)
normalList.append(normalFeatLinks.remove(normalFeatLinks.firstKey()));
while (epicFeatLinks.size() > 0)
epicList.append(epicFeatLinks.remove(epicFeatLinks.firstKey()));
// Parse through masterfeats
for (FeatEntry masterfeat : masterFeats.values()) {
if (masterfeat.isClassFeat && masterfeat.allChildrenClassFeat) continue;
if (masterfeat.isEpic)
epicMasterfeatLinks.put(masterfeat.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
masterfeat.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", masterfeat.name));
if (!masterfeat.allChildrenEpic)
normalMasterfeatLinks.put(masterfeat.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
masterfeat.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", masterfeat.name));
}
// Transfer the masterfeat link lists into text form
while (normalMasterfeatLinks.size() > 0)
normalMasterList.append(normalMasterfeatLinks.remove(normalMasterfeatLinks.firstKey()));
while (epicMasterfeatLinks.size() > 0)
epicMasterList.append(epicMasterfeatLinks.remove(epicMasterfeatLinks.firstKey()));
// Add in a link to the page listing *all* feats
normalMenu = normalMenu.replaceAll("~~~allFeatsLink~~~", menuItemTemplate.replaceAll("~~~TargetPath~~~",
(contentPath + "feats" + fileSeparator + "alphasortedfeats.html")
.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", curLanguageData[LANGDATA_ALLFEATSTXT]));
// Add in a link to the page listing all epic feats
epicMenu = epicMenu.replaceAll("~~~allFeatsLink~~~", menuItemTemplate.replaceAll("~~~TargetPath~~~",
(contentPath + "epic_feats" + fileSeparator + "alphasortedepicfeats.html")
.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", curLanguageData[LANGDATA_ALLEPICFEATSTXT]));
// Add in the masterfeat links
normalMenu = normalMenu.replaceAll("~~~masterFeats~~~", normalMasterList.toString());
epicMenu = epicMenu.replaceAll("~~~masterFeats~~~", epicMasterList.toString());
// Add in the feat links
normalMenu = normalMenu.replaceAll("~~~featLinks~~~", normalList.toString());
epicMenu = epicMenu.replaceAll("~~~featLinks~~~", epicList.toString());
// Print the pages
printPage(menuPath + "manual_menus_feat.html", normalMenu);
printPage(menuPath + "manual_menus_epic_feat.html", epicMenu);
}
//private static void doFeatMenusAux()
/**
* Sorts the classes into alphabetic order using a TreeMap, and prints a menu
* page out of the results. Base and prestige classes get their own menus
*/
public static void doClassMenus() {
TreeMap<String, String> baseLinks = new TreeMap<String, String>(),
prestigeLinks = new TreeMap<String, String>();
StringBuffer basePrint = new StringBuffer(),
prestigePrint = new StringBuffer();
String temp = null;
if (verbose) System.out.println("Printing class menus");
for (ClassEntry clazz : classes.values()) {
if (clazz.isBase)
baseLinks.put(clazz.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
clazz.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", clazz.name));
else
prestigeLinks.put(clazz.name, menuItemTemplate.replaceAll("~~~TargetPath~~~",
clazz.filePath.replace(mainPath, "../").replaceAll("\\\\", "/"))
.replaceAll("~~~targetName~~~", clazz.name));
}
while (baseLinks.size() > 0)
basePrint.append(baseLinks.remove(baseLinks.firstKey()));
while (prestigeLinks.size() > 0)
prestigePrint.append(prestigeLinks.remove(prestigeLinks.firstKey()));
printPage(menuPath + "manual_menus_base_classes.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_BASECLASSESTXT])
.replaceAll("~~~menuEntries~~~", basePrint.toString()));
printPage(menuPath + "manual_menus_prestige_classes.html", menuTemplate.replaceAll("~~~menuName~~~", curLanguageData[LANGDATA_PRESTIGECLASSESTXT])
.replaceAll("~~~menuEntries~~~", prestigePrint.toString()));
}
}

View File

@@ -0,0 +1,602 @@
package prc.autodoc;
import java.util.List;
import java.util.TreeMap;
import static prc.Main.err_pr;
import static prc.Main.verbose;
import static prc.autodoc.Main.*;
/**
* This class contains the methods for manual page generation.
*
* @author Ornedan
*/
public final class PageGeneration {
private PageGeneration() {/* No need for instantiation */}
/**
* Handles printing of the skill pages.
*/
public static void printSkills() {
String text = null;
for (GenericEntry skill : skills.values()) {
if (verbose) System.out.println("Printing page for " + skill.name);
// Start building the entry data. First, place in the name
text = skillTemplate;
text = text.replaceAll("~~~SkillName~~~", skill.name);
// Then, put in the description
text = text.replaceAll("~~~SkillTLKDescription~~~",
skill.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", skill.iconPath);
// Print the page
try {
printPage(skill.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for skill " + skill.entryNum + ": " + skill.name + ":\n" + e);
}
}
System.gc();
}
/**
* Handles printing of the crafting property pages.
*/
public static void printCrafting() {
String text = null;
for (GenericEntry craft_armour_var : craft_armour.values()) {
if (verbose) System.out.println("Printing page for " + craft_armour_var.name);
// Start building the entry data. First, place in the name
text = craftTemplate;
text = text.replaceAll("~~~CraftPropName~~~", craft_armour_var.name);
// Then, put in the description
text = text.replaceAll("~~~CraftPropTLKDescription~~~",
craft_armour_var.text);
// Print the page
try {
printPage(craft_armour_var.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for skill " + craft_armour_var.entryNum + ": " + craft_armour_var.name + ":\n" + e);
}
}
for (GenericEntry craft_weapon_var : craft_weapon.values()) {
if (verbose) System.out.println("Printing page for " + craft_weapon_var.name);
// Start building the entry data. First, place in the name
text = craftTemplate;
text = text.replaceAll("~~~CraftPropName~~~", craft_weapon_var.name);
// Then, put in the description
text = text.replaceAll("~~~CraftPropTLKDescription~~~",
craft_weapon_var.text);
// Print the page
try {
printPage(craft_weapon_var.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for skill " + craft_weapon_var.entryNum + ": " + craft_weapon_var.name + ":\n" + e);
}
}
for (GenericEntry craft_ring_var : craft_ring.values()) {
if (verbose) System.out.println("Printing page for " + craft_ring_var.name);
// Start building the entry data. First, place in the name
text = craftTemplate;
text = text.replaceAll("~~~CraftPropName~~~", craft_ring_var.name);
// Then, put in the description
text = text.replaceAll("~~~CraftPropTLKDescription~~~",
craft_ring_var.text);
// Print the page
try {
printPage(craft_ring_var.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for skill " + craft_ring_var.entryNum + ": " + craft_ring_var.name + ":\n" + e);
}
}
for (GenericEntry craft_wondrous_var : craft_wondrous.values()) {
if (verbose) System.out.println("Printing page for " + craft_wondrous_var.name);
// Start building the entry data. First, place in the name
text = craftTemplate;
text = text.replaceAll("~~~CraftPropName~~~", craft_wondrous_var.name);
// Then, put in the description
text = text.replaceAll("~~~CraftPropTLKDescription~~~",
craft_wondrous_var.text);
// Print the page
try {
printPage(craft_wondrous_var.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for skill " + craft_wondrous_var.entryNum + ": " + craft_wondrous_var.name + ":\n" + e);
}
}
System.gc();
}
/**
* Prints all spells and spell-likes (psionics, truenaming).
* As of now, all of these are similar enough to share the same
* template, so they can be done here together.
*/
public static void printSpells() {
String text = null;
StringBuilder subradialText = null;
for (SpellEntry spell : spells.values()) {
if (verbose) System.out.println("Printing page for " + spell.name);
// Start building the entry data. First, place in the name
text = spellTemplate;
text = text.replaceAll("~~~SpellName~~~", spell.name);
// Then, put in the description
text = text.replaceAll("~~~SpellTLKDescription~~~",
spell.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", spell.iconPath);
// Handle subradials, if any
subradialText = new StringBuilder();
if (spell.subradials != null) {
for (Tuple<String, String> subradial : spell.subradials) {
subradialText.append(spellSubradialListEntryTemplate.replaceAll("~~~Icon~~~", subradial.e2)
.replaceAll("~~~SubradialName~~~", subradial.e1));
}
subradialText = new StringBuilder(spellSubradialListTemplate.replaceAll("~~~EntryList~~~", subradialText.toString()));
}
text = text.replaceAll("~~~SubradialNames~~~", subradialText.toString());
// Print the page
try {
printPage(spell.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for spell " + spell.entryNum + ": " + spell.name + ":\n" + e);
}
}
// 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();
}
/**
* A simple method for printing out all the feat pages.
*/
public static void printFeats() {
// Print feats
printFeatsAux();
// Print masterfeats
printMasterFeatsAux();
// Print a page with alphabetically sorted list of all feats
printPage(contentPath + "feats" + fileSeparator + "alphasortedfeats.html", buildAllFeatsList(false));
// Print a page with alphabetically sorted list of all epic feats
printPage(contentPath + "epic_feats" + fileSeparator + "alphasortedepicfeats.html", buildAllFeatsList(true));
}
/**
* Prints the masterfeat pages.
*/
private static void printMasterFeatsAux() {
String text = null,
temp = null;
for (FeatEntry masterfeat : masterFeats.values()) {
if (verbose) System.out.println("Printing page for " + masterfeat.name);
// Build the entry data
text = mFeatTemplate;
text = text.replaceAll("~~~FeatName~~~",
masterfeat.name);
text = text.replaceAll("~~~FeatTLKDescription~~~",
masterfeat.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", masterfeat.iconPath);
// Add in child feats
temp = "";
for (FeatEntry child : masterfeat.childFeats.values()) {
temp += pageLinkTemplate.replace("~~~Path~~~", child.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", child.name);
}
text = text.replaceAll("~~~MasterFeatChildList~~~", temp);
// Print the page
try {
printPage(masterfeat.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for masterfeat " + masterfeat.entryNum + ": " + masterfeat.name + ":\n" + e);
}
}
System.gc();
}
/**
* Prints the feat pages.
*/
private static void printFeatsAux() {
String text = null,
temp = null;
StringBuilder subradialText = null;
for (FeatEntry feat : feats.values()) {
if (verbose) System.out.println("Printing page for " + feat.name);
// Build the entry data
text = featTemplate;
text = text.replaceAll("~~~FeatName~~~",
feat.name);
text = text.replaceAll("~~~FeatTLKDescription~~~",
feat.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", feat.iconPath);
// Print prerequisites into the entry
temp = "";
if (feat.andRequirements.size() != 0) {
temp += prereqANDFeatHeaderTemplate;
for (FeatEntry andReq : feat.andRequirements.values())
temp += pageLinkTemplate.replace("~~~Path~~~", andReq.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", andReq.name);
}
if (feat.orRequirements.size() != 0) {
temp += prereqORFeatHeaderTemplate;
for (FeatEntry orReq : feat.orRequirements.values())
temp += pageLinkTemplate.replace("~~~Path~~~", orReq.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", orReq.name);
}
text = text.replaceAll("~~~PrerequisiteFeatList~~~", temp);
// Print the successor, if any, into the entry
temp = "";
if (feat.successor != null) {
temp += successorFeatHeaderTemplate + pageLinkTemplate.replace("~~~Path~~~", feat.successor.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", feat.successor.name);
}
text = text.replaceAll("~~~SuccessorFeat~~~", temp);
// Handle subradials, if any
subradialText = new StringBuilder();
if (feat.subradials != null) {
for (Tuple<String, String> subradial : feat.subradials) {
subradialText.append(spellSubradialListEntryTemplate.replaceAll("~~~Icon~~~", subradial.e2)
.replaceAll("~~~SubradialName~~~", subradial.e1));
}
subradialText = new StringBuilder(spellSubradialListTemplate.replaceAll("~~~EntryList~~~", subradialText.toString()));
}
text = text.replaceAll("~~~SubradialNames~~~", subradialText.toString());
// Handle feats that have this as their prerequisite
temp = "";
if (feat.requiredForFeats.size() != 0) {
temp += requiredForFeatHeaderTemplate;
for (FeatEntry req : feat.requiredForFeats.values()) {
temp += pageLinkTemplate.replace("~~~Path~~~", req.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", req.name);
}
}
text = text.replaceAll("~~~RequiredForFeatList~~~", temp);
// Print the page
try {
printPage(feat.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for feat " + feat.entryNum + ": " + feat.name + ":\n" + e);
}
}
System.gc();
}
/**
* Constructs an alphabetically sorted list of all (or only all epic) feats.
*
* @param epicOnly if <code>true</code>, only feats that are epic are placed in the list. Otherwise, all feats.
* @return an html page containing the list
*/
private static String buildAllFeatsList(boolean epicOnly) {
TreeMap<String, FeatEntry> sorted = new TreeMap<String, FeatEntry>(String.CASE_INSENSITIVE_ORDER);
for (FeatEntry entry : feats.values())
if (!epicOnly || (epicOnly && entry.isEpic))
sorted.put(entry.name, entry);
String toReturn = alphaSortedListTemplate,
entrySet;
FeatEntry entry;
char cha = (char) 0;
int counter = 0;
boolean addedAny;
while (sorted.size() > 0) {
// Build the list for a single letter
entrySet = listEntrySetTemplate.replace("~~~LinkId~~~", new String(new char[]{cha}))
.replace("~~~EntrySetName~~~", new String(new char[]{cha}).toUpperCase());
addedAny = false;
while (sorted.size() > 0 &&
sorted.firstKey().toLowerCase().startsWith(new String(new char[]{cha}))) {
addedAny = true;
entry = sorted.remove(sorted.firstKey());
entrySet = entrySet.replace("~~~FeatList~~~", listEntryTemplate.replace("~~~EvenOrOdd~~~", (counter++ % 2) == 0 ? "even" : "odd")
.replace("~~~EntryPath~~~",
entry.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~EntryName~~~", entry.name)
+ "~~~FeatList~~~");
}
entrySet = entrySet.replace("~~~FeatList~~~", "");
cha++;
// Add the sublist to the page
if (addedAny)
toReturn = toReturn.replace("~~~Content~~~", entrySet + "\n" + "~~~Content~~~");
}
// Clear off the last replacement marker
toReturn = toReturn.replace("~~~Content~~~", "");
return toReturn;
}
/**
* Handles creation of the domain pages.
*/
public static void printDomains() {
String text = null;
StringBuffer spellList = null;
for (DomainEntry domain : domains.values()) {
if (verbose) System.out.println("Printing page for " + domain.name);
// Build the entry data
text = domainTemplate;
text = text.replaceAll("~~~DomainName~~~",
domain.name);
text = text.replaceAll("~~~DomainTLKDescription~~~",
domain.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", domain.iconPath);
// Add a link to the granted feat
text = text.replaceAll("~~~DomainFeat~~~",
pageLinkTemplate.replace("~~~Path~~~", domain.grantedFeat.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", domain.grantedFeat.name));
// Add links to the granted spells
spellList = new StringBuffer();
for (SpellEntry grantedSpell : domain.spells) {
spellList.append(pageLinkTemplate.replace("~~~Path~~~", grantedSpell.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", grantedSpell.name));
}
text = text.replaceAll("~~~DomainSpellList~~~", spellList.toString());
// Print the page
try {
printPage(domain.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for domain " + domain.entryNum + ": " + domain.name + ":\n" + e);
}
}
System.gc();
}
/**
* Handles creation of the race pages.
*/
public static void printRaces() {
String text = null;
StringBuffer featList = null;
for (RaceEntry race : races.values()) {
if (verbose) System.out.println("Printing page for " + race.name);
// Build the entry data
text = raceTemplate;
text = text.replaceAll("~~~RaceName~~~",
race.name);
text = text.replaceAll("~~~RaceTLKDescription~~~",
race.text);
// Add links to the racial feats
featList = new StringBuffer();
for (FeatEntry grantedFeat : race.raceFeats.values()) {
featList.append(pageLinkTemplate.replace("~~~Path~~~", grantedFeat.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", grantedFeat.name));
}
text = text.replaceAll("~~~RaceFeats~~~", featList.toString());
// Print the page
try {
printPage(race.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for race " + race.entryNum + ": " + race.name + ":\n" + e);
}
}
System.gc();
}
/**
* Handles creation of the class pages.
* Subsections handled by several following methods.
*/
public static void printClasses() {
String text = null,
temp = null;
String[] tempArr = null;
for (ClassEntry class_ : classes.values()) {
if (verbose) System.out.println("Printing page for " + class_.name);
// Build the entry data
text = classTemplate;
text = text.replaceAll("~~~ClassName~~~",
class_.name);
text = text.replaceAll("~~~ClassTLKDescription~~~",
class_.text);
// Add in the icon
text = text.replaceAll("~~~Icon~~~", class_.iconPath);
// Add in the BAB and saving throws table
text = text.replaceAll("~~~ClassBABAndSavThrTable~~~", buildBabAndSaveTable(class_));
// Add in the skills table
text = text.replaceAll("~~~ClassSkillTable~~~", buildSkillTable(class_));
// Add in the feat table
text = text.replaceAll("~~~ClassFeatTable~~~", buildClassFeatTables(class_));
// Add in the spells / powers table
text = text.replaceAll("~~~ClassSpellAndPowerTables~~~", buildClassSpellAndPowerTables(class_));
// Print the page
try {
printPage(class_.filePath, text);
} catch (PageGenerationException e) {
err_pr.println("Error: Exception when writing page for class " + class_.entryNum + ": " + class_.name + ":\n" + e);
}
}
System.gc();
}
/**
* Constructs the html table of levels and their bab + saving throw bonus values.
*
* @param class_ The class entry data structure of the class to generate the table for
* @return string representation of the table
*/
private static String buildBabAndSaveTable(ClassEntry class_) {
String toReturn = "";
if (class_.babSav.size() != 0) {
toReturn += babAndSavthrTableHeaderTemplate + "\n";
// Start building the table
for (int i = 0; i < class_.babSav.size(); i++) {
toReturn += "<tr>\n";
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", (i + 1) + "");
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", class_.babSav.get(i)[0]);
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", class_.babSav.get(i)[1]);
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", class_.babSav.get(i)[2]);
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", class_.babSav.get(i)[3]);
toReturn += "</tr>\n";
}
toReturn += "</table>\n";
}
return toReturn;
}
/**
* Constructs the html table of the class & cross-class skills of this class.
* TreeMaps are used to arrange the printed skills in alphabetic order.
*
* @param class_ The class entry data structure of the class to generate the table for
* @return string representation of the table
*/
private static String buildSkillTable(ClassEntry class_) {
String toReturn = skillTableHeaderTemplate;
GenericEntry tempSkill = null;
// Clone the maps, since we'll be performing destructive operations on them
TreeMap<String, GenericEntry> classSkills = new TreeMap<String, GenericEntry>(class_.skillList.e1),
crossClassSkills = new TreeMap<String, GenericEntry>(class_.skillList.e2);
while (classSkills.size() > 0 || crossClassSkills.size() > 0) {
toReturn += "<tr>\n";
if (classSkills.size() > 0) {
tempSkill = classSkills.remove(classSkills.firstKey());
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", "<a href=\"" + tempSkill.filePath.replace(contentPath, "../").replaceAll("\\\\", "/") + "\" target=\"content\">" + tempSkill.name + "</a>");
} else
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", "&nbsp;");
if (crossClassSkills.size() > 0) {
tempSkill = crossClassSkills.remove(crossClassSkills.firstKey());
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", "<a href=\"" + tempSkill.filePath.replace(contentPath, "../").replaceAll("\\\\", "/") + "\" target=\"content\">" + tempSkill.name + "</a>");
} else
toReturn += classTablesEntryTemplate.replaceAll("~~~Entry~~~", "&nbsp;");
toReturn += "</tr>\n";
}
toReturn += "</table>\n";
return toReturn;
}
/**
* Constructs the html table of the bonus and selectable class feats of the given class.
* TreeMaps are used to arrange the printed feats in alphabetic order.
*
* @param class_ The class entry data structure of the class to generate the table for
* @return String that contains the table
*/
private static String buildClassFeatTables(ClassEntry class_) {
List<TreeMap<String, FeatEntry>> grantedFeatList = class_.featList.e2.e1,
selectableFeatList = class_.featList.e2.e2;
List<Integer> bonusFeatCounts = class_.featList.e1;
// Start constructing the table
StringBuffer tableText = new StringBuffer();
StringBuffer linkList = null;
String tableLine = null;
for (int i = 0; i < grantedFeatList.size(); i++) {
tableLine = classFeatTableEntryTemplate.replace("~~~Level~~~", String.valueOf(i + 1))
.replace("~~~NumberOfBonusFeats~~~", bonusFeatCounts.get(i).toString());
// Generate the granted feats list
linkList = new StringBuffer();
if (grantedFeatList.get(i) != null)
for (FeatEntry feat : grantedFeatList.get(i).values()) {
linkList.append(pageLinkTemplate.replace("~~~Path~~~", feat.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", feat.name));
}
else
linkList.append("&nbsp;");
tableLine = tableLine.replace("~~~FeatsGrantedList~~~", linkList.toString());
// Generate the granted feats list
linkList = new StringBuffer();
if (selectableFeatList.get(i) != null)
for (FeatEntry feat : selectableFeatList.get(i).values()) {
linkList.append(pageLinkTemplate.replace("~~~Path~~~", feat.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", feat.name));
}
else
linkList.append("&nbsp;");
tableLine = tableLine.replace("~~~SelectableFeatsList~~~", linkList.toString());
// Append the line to the table
tableText.append(tableLine);
}
return classFeatTableTemplate.replace("~~~TableContents~~~", tableText.toString());
}
/**
* Constructs the html table of the new spellbook spells and psionic powers lists of
* the given class. The entries are ordered by spell / power level
*
* @param class_ The class entry data structure of the class to generate the table for
* @return String that contains the table
*/
private static String buildClassSpellAndPowerTables(ClassEntry class_) {
StringBuffer toReturn = new StringBuffer();
for (Tuple<Tuple<String, String>, TreeMap<Integer, TreeMap<String, SpellEntry>>> magicData : class_.magics) {
// Map of level numbers to maps of spell names to html links. Cloned due to destructive operations
TreeMap<Integer, TreeMap<String, SpellEntry>> levelLists =
new TreeMap<Integer, TreeMap<String, SpellEntry>>(magicData.e2);
StringBuffer tableLines = new StringBuffer(),
spellLinks;
String tableLine;
while (levelLists.size() > 0) {
tableLine = classMagicTableEntryTemplate.replace("~~~Level~~~", levelLists.firstKey().toString());
spellLinks = new StringBuffer();
for (SpellEntry spell : levelLists.remove(levelLists.firstKey()).values())
spellLinks.append(pageLinkTemplate.replace("~~~Path~~~", spell.filePath.replace(contentPath, "../").replaceAll("\\\\", "/"))
.replace("~~~Name~~~", spell.name));
tableLines.append(tableLine.replace("~~~EntryList~~~", spellLinks.toString()));
}
toReturn.append(classMagicTableTemplate.replace("~~~TableName~~~", magicData.e1.e1)
.replace("~~~Type~~~", magicData.e1.e2)
.replace("~~~TableContents~~~", tableLines.toString()));
}
return toReturn.toString();
}
}

View File

@@ -0,0 +1,23 @@
package prc.autodoc;
/**
* An exception indicating failure while generating a page.
*/
public class PageGenerationException extends java.lang.RuntimeException{
private static final long serialVersionUID = 0x2L;
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
*/
public PageGenerationException(String message) { super(message); }
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
* @param cause Another exception that caused this one to be thrown
*/
public PageGenerationException(String message, Throwable cause){ super(message, cause); }
}

Binary file not shown.

View File

@@ -0,0 +1,29 @@
package prc.autodoc;
import java.util.TreeMap;
/**
* Data structure for a race entry.
*/
public class RaceEntry extends GenericEntry {
/**
* A list of masterfeat's children
*/
public final TreeMap<String, FeatEntry> raceFeats;
/**
* The constructor.
*
* @param name Name of the race
* @param text Description of the race
* @param filePath Path where the html page for this race will be written to
* @param entryNum racialtypes.2da index
* @param raceFeats Racial feat list
*/
public RaceEntry(String name, String text, String filePath,
int entryNum, TreeMap<String, FeatEntry> raceFeats) {
super(name, text, null, filePath, entryNum);
this.raceFeats = raceFeats;
}
}

Binary file not shown.

View File

@@ -0,0 +1,38 @@
package prc.autodoc;
import prc.autodoc.Main.SpellType;
import java.util.List;
/**
* Data structure for a spell entry.
*/
public class SpellEntry extends GenericEntry {
/**
* Type of this spell entry
*/
public final SpellType type;
/**
* The spell's subradials, if any. List of spell name, icon path tuples.
*/
public final List<Tuple<String, String>> subradials;
/**
* The constructor.
*
* @param name Name of the spell
* @param text Description of the spell
* @param iconPath Path of the spell's icon
* @param filePath Path of the html file describing the spell.
* @param entryNum spells.2da index
* @param type Type of the spell: Normal / Epic / Psionic / whatever
* @param subradials
*/
public SpellEntry(String name, String text, String iconPath, String filePath,
int entryNum, SpellType type, List<Tuple<String, String>> subradials) {
super(name, text, iconPath, filePath, entryNum);
this.type = type;
this.subradials = subradials;
}
}

Binary file not shown.

View File

@@ -0,0 +1,34 @@
package prc.autodoc;
import java.util.Date;
/**
* An absolutely critical part of the Document Creator, not. Prints a spinning line
* for the user to look at while the program is working.
*/
public final class Spinner {
private final char[] states = new char[]{'|', '/', '-', '\\'};
private int curState = 0;
private boolean active = true;
private Date lastSpinTime = new Date();
/**
* Spins the spinner.
*/
public void spin() {
long minTimeBetweenSpinsInMS = 300;
if (active && new Date().getTime() - lastSpinTime.getTime() > minTimeBetweenSpinsInMS) {
System.out.print(states[curState = ++curState % states.length] + "\u0008");
lastSpinTime = new Date();
}
}
/**
* Turns the spinner off.
*/
public void disable() {
active = false;
}
}

View File

@@ -0,0 +1,23 @@
package prc.autodoc;
/**
* An exception indicating TLK read failed.
*/
public class TLKReadException extends java.lang.RuntimeException{
private static final long serialVersionUID = 0x0L;
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
*/
public TLKReadException(String message) { super(message); }
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
* @param cause Another exception that caused this one to be thrown
*/
public TLKReadException(String message, Throwable cause){ super(message, cause); }
}

Binary file not shown.

View File

@@ -0,0 +1,30 @@
package prc.autodoc;
/**
* A simple tuple class
*
* @param <T1> The type of the first element of the tuple
* @param <T2> The type of the second element of the tuple
* @author Ornedan
*/
public class Tuple<T1, T2> {
/**
* The first element
*/
public final T1 e1;
/**
* The second element
*/
public final T2 e2;
/**
* Generates a new tuple out of the given two objects.
*
* @param e1 The object to become the first element of the new tuple
* @param e2 The object to become the second element of the new tuple
*/
public Tuple(T1 e1, T2 e2) {
this.e1 = e1;
this.e2 = e2;
}
}

View File

@@ -0,0 +1,23 @@
package prc.autodoc;
/**
* An exception indicating 2da read failed.
*/
public class TwoDAReadException extends java.lang.RuntimeException{
private static final long serialVersionUID = 0x1L;
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
*/
public TwoDAReadException(String message) { super(message); }
/**
* Creates a new exception.
*
* @param message The message this exception is to carry
* @param cause Another exception that caused this one to be thrown
*/
public TwoDAReadException(String message, Throwable cause){ super(message, cause); }
}